/*-------------------------------------------------------------------------
 *
 * pathnode.c
 *	  操作路径列表和创建路径节点的例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/optimizer/util/pathnode.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <math.h>

#include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/extensible.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/appendinfo.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/optimizer.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/placeholder.h"
#include "optimizer/planmain.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
#include "optimizer/tlist.h"
#include "parser/parsetree.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/selfuncs.h"

typedef enum
{
	COSTS_EQUAL,				/* 路径成本模糊相等 */
	COSTS_BETTER1,				/* 第一条路径比第二条便宜 */
	COSTS_BETTER2,				/* 第二条路径比第一条便宜 */
	COSTS_DIFFERENT				/* 两条路径在成本上不相互支配 */
} PathCostComparison;

/*
 * STD_FUZZ_FACTOR 是 compare_path_costs_fuzzily 的正常模糊因子。
 * XXX 是否值得使其可由用户控制？
 * 这在规划器运行时间和路径成本比较的准确性之间提供了一个权衡。
 */
#define STD_FUZZ_FACTOR 1.01

static List *fc_translate_sub_tlist(List *fc_tlist, int fc_relid);
static int	fc_append_total_cost_compare(const ListCell *fc_a, const ListCell *fc_b);
static int	fc_append_startup_cost_compare(const ListCell *fc_a, const ListCell *fc_b);
static List *fc_reparameterize_pathlist_by_child(PlannerInfo *fc_root,
											  List *fc_pathlist,
											  RelOptInfo *fc_child_rel);
static bool fc_contain_references_to(PlannerInfo *fc_root, Node *fc_clause,
								  Relids fc_relids);
static bool fc_ris_contain_references_to(PlannerInfo *fc_root, List *fc_rinfos,
									  Relids fc_relids);




/*
 * compare_path_costs
 *	  返回 -1，0 或 +1，具体取决于 path1 是否比 path2 更便宜、成本相同，
 *	  或更贵，依据指定标准。
 */
int compare_path_costs(Path *fc_path1, Path *fc_path2, CostSelector fc_criterion)
{
	if (fc_criterion == STARTUP_COST)
	{
		if (fc_path1->startup_cost < fc_path2->startup_cost)
			return -1;
		if (fc_path1->startup_cost > fc_path2->startup_cost)
			return +1;

		/*
		 * 如果路径具有相同的启动成本（这很有可能），按总成本对它们进行排序。
		 */
		if (fc_path1->total_cost < fc_path2->total_cost)
			return -1;
		if (fc_path1->total_cost > fc_path2->total_cost)
			return +1;
	}
	else
	{
		if (fc_path1->total_cost < fc_path2->total_cost)
			return -1;
		if (fc_path1->total_cost > fc_path2->total_cost)
			return +1;

		/*
		 * 如果路径具有相同的总成本，按启动成本对它们进行排序。
		 */
		if (fc_path1->startup_cost < fc_path2->startup_cost)
			return -1;
		if (fc_path1->startup_cost > fc_path2->startup_cost)
			return +1;
	}
	return 0;
}

/*
 * compare_fractional_path_costs
 *	  返回 -1，0 或 +1，具体取决于 path1 是否比 path2 更便宜、成本相同，
 *	  或更贵，以获取指定比例的总元组。
 *
 * 如果比例 <= 0 或 > 1，理解为 1，即选择具有更低总成本的路径。
 */
int compare_fractional_path_costs(Path *fc_path1, Path *fc_path2,
							  double fc_fraction)
{
	Cost		fc_cost1,
				fc_cost2;

	if (fc_fraction <= 0.0 || fc_fraction >= 1.0)
		return compare_path_costs(fc_path1, fc_path2, TOTAL_COST);
	fc_cost1 = fc_path1->startup_cost +
		fc_fraction * (fc_path1->total_cost - fc_path1->startup_cost);
	fc_cost2 = fc_path2->startup_cost +
		fc_fraction * (fc_path2->total_cost - fc_path2->startup_cost);
	if (fc_cost1 < fc_cost2)
		return -1;
	if (fc_cost1 > fc_cost2)
		return +1;
	return 0;
}

/*
 * compare_path_costs_fuzzily
 *	  比较两条路径的成本，以查看是否可以说其中一条主导另一条。
 *
 * 我们使用模糊比较，以使 add_path() 可以避免保留一对实际成本差异微不足道的路径。
 *
 * fuzz_factor 参数必须为 1.0 加上 delta，其中 delta 是被认为是显著差异的较小成本的比例。 
 * 例如，fuzz_factor = 1.01 使模糊性限制为较小成本的 1%。
 *
 * 如果启动成本和总成本都模糊相同，则称这两条路径的成本是“相等的”。 
 * 如果 path1 的启动成本模糊更好且总成本模糊没有更差，则称 path1 优于 path2，
 * 或者如果 path1 的总成本模糊更好且启动成本模糊没有更差。
 * 如果反之成立，则 path2 优于 path1。最后，如果一条路径在启动成本上模糊更好而在 
 * 总成本上模糊更差，我们则只说它们的成本是“不同的”，因为在整个性能范围内，
 * 两者都不主导对方。
 *
 * 此功能还执行政策规则，即在 parent->consider_startup 和 
 * parent->consider_param_startup 中相关的一个为假时，不允许在仅根据良好的启动成本的基础上生存比较，
 * 因此当总成本较低者为真时，我们绝不会返回 COSTS_DIFFERENT。
 * （但如果总成本模糊相等，我们仍然比较启动成本，
 * 希望消除某一条路径。）
 */
static PathCostComparison
fc_compare_path_costs_fuzzily(Path *fc_path1, Path *fc_path2, double fc_fuzz_factor)
{
#define CONSIDER_PATH_STARTUP_COST(p)  \
	((p)->param_info == NULL ? (p)->parent->consider_startup : (p)->parent->consider_param_startup)

	/*
	 * 首先检查总成本，因为它更有可能不同；许多路径的启动成本为零。
	 */
	if (fc_path1->total_cost > fc_path2->total_cost * fc_fuzz_factor)
	{
		/* path1 在总成本上模糊更差 */
		if (CONSIDER_PATH_STARTUP_COST(fc_path1) &&
			fc_path2->startup_cost > fc_path1->startup_cost * fc_fuzz_factor)
		{
			/* ... 但 path2 在启动成本上模糊更差，因此视为不同 */
			return COSTS_DIFFERENT;
		}
		/* 否则 path2 主导 */
		return COSTS_BETTER2;
	}
	if (fc_path2->total_cost > fc_path1->total_cost * fc_fuzz_factor)
	{
		/* path2 在总成本上模糊更差 */
		if (CONSIDER_PATH_STARTUP_COST(fc_path2) &&
			fc_path1->startup_cost > fc_path2->startup_cost * fc_fuzz_factor)
		{
			/* ... 但 path1 在启动成本上模糊更差，因此视为不同 */
			return COSTS_DIFFERENT;
		}
		/* 否则 path1 主导 */
		return COSTS_BETTER1;
	}
	/* 在总成本上模糊相同 ... */
	if (fc_path1->startup_cost > fc_path2->startup_cost * fc_fuzz_factor)
	{
		/* ... 但 path1 在启动成本上模糊更差，因此 path2 胜出 */
		return COSTS_BETTER2;
	}
	if (fc_path2->startup_cost > fc_path1->startup_cost * fc_fuzz_factor)
	{
		/* ... 但 path2 在启动成本上模糊更差，因此 path1 胜出 */
		return COSTS_BETTER1;
	}
	/* 在两个成本上模糊相同 */
	return COSTS_EQUAL;

#undef CONSIDER_PATH_STARTUP_COST
}

/*
 * set_cheapest
 *	  从一个关系的路径中找到最低成本的路径，
 *	  并将其保存在关系的 cheapest-path 字段中。
 *
 * cheapest_total_path 通常是最低成本的无参数化路径；但如果没有无参数化路径，
 * 我们分配它为最佳（最低参数化的）参数化路径。 然而，仅考虑无参数化路径作为
 * cheapest_startup_path 的候选人，因此如果没有无参数化路径，它将为 NULL。
 *
 * cheapest_parameterized_paths 列表收集在此关系的 add_path() 比赛中存活的所有参数化路径。
 * （由于 add_path 忽略参数化路径的 pathkeys，这些路径将是路径
 * 成本最佳或行数最佳的路径。我们可能还会在某些情况下为相同参数化
 * 有一个支持并行和不支持并行的路径，但在大多数情况下，所有路径
 * 都将是支持并行的或都不支持并行的，因此这种情况相对较少。）
 *
 * cheapest_parameterized_paths 通常也包括 cheapest-total 的
 * 无参数化路径；如果有一个，用户会发现包含它更方便。
 *
 * 这通常仅在我们完成构建关系节点的路径列表后调用。
 */
void set_cheapest(RelOptInfo *fc_parent_rel)
{
	Path	   *fc_cheapest_startup_path;
	Path	   *fc_cheapest_total_path;
	Path	   *fc_best_param_path;
	List	   *fc_parameterized_paths;
	ListCell   *fc_p;

	Assert(IsA(fc_parent_rel, RelOptInfo));

	if (fc_parent_rel->pathlist == NIL)
		elog(ERROR, "could not devise a query plan for the given query");

	fc_cheapest_startup_path = fc_cheapest_total_path = fc_best_param_path = NULL;
	fc_parameterized_paths = NIL;

	foreach(fc_p, fc_parent_rel->pathlist)
	{
		Path	   *fc_path = (Path *) lfirst(fc_p);
		int			fc_cmp;

		if (fc_path->param_info)
		{
			/* 参数化路径，因此将其添加到 parameterized_paths */
			fc_parameterized_paths = lappend(fc_parameterized_paths, fc_path);

			/*
			 * 如果我们有无参数化的最低成本路径，我们不再关心寻找最佳参数化路径，
			 * 所以继续前进。
			 */
			if (fc_cheapest_total_path)
				continue;

			/*
			 * 否则，跟踪最佳参数化路径，即在最低
			 * 参数化中的所有路径中总成本最低的路径。
			 */
			if (fc_best_param_path == NULL)
				fc_best_param_path = fc_path;
			else
			{
				switch (bms_subset_compare(PATH_REQ_OUTER(fc_path),
										   PATH_REQ_OUTER(fc_best_param_path)))
				{
					case BMS_EQUAL:
						/* 保留更便宜的一个 */
						if (compare_path_costs(fc_path, fc_best_param_path,
											   TOTAL_COST) < 0)
							fc_best_param_path = fc_path;
						break;
					case BMS_SUBSET1:
						/* 新路径参数化较少 */
						fc_best_param_path = fc_path;
						break;
					case BMS_SUBSET2:
						/* 旧路径参数化程度较低，保留它 */
						break;
					case BMS_DIFFERENT:

						/*
						 * 这意味着两个路径都没有对关系进行最小可能的
						 * 参数化。我们将保留旧路径，直到出现更好的选择。
						 */
						break;
				}
			}
		}
		else
		{
			/* 未参数化路径，因此考虑将其放入最便宜的槽中 */
			if (fc_cheapest_total_path == NULL)
			{
				fc_cheapest_startup_path = fc_cheapest_total_path = fc_path;
				continue;
			}

			/*
			 * 如果我们找到两个成本相同的路径，尝试保留排序较好的一个。
			 * 这些路径可能具有无关的排序顺序，在这种情况下我们只能猜测
			 * 哪个可能更好，但如果一个优于另一个，我们肯定应该保留那个。
			 */
			fc_cmp = compare_path_costs(fc_cheapest_startup_path, fc_path, STARTUP_COST);
			if (fc_cmp > 0 ||
				(fc_cmp == 0 &&
				 compare_pathkeys(fc_cheapest_startup_path->pathkeys,
								  fc_path->pathkeys) == PATHKEYS_BETTER2))
				fc_cheapest_startup_path = fc_path;

			fc_cmp = compare_path_costs(fc_cheapest_total_path, fc_path, TOTAL_COST);
			if (fc_cmp > 0 ||
				(fc_cmp == 0 &&
				 compare_pathkeys(fc_cheapest_total_path->pathkeys,
								  fc_path->pathkeys) == PATHKEYS_BETTER2))
				fc_cheapest_total_path = fc_path;
		}
	}

	/* 将最便宜的未参数化路径（如果有）添加到 parameterized_paths */
	if (fc_cheapest_total_path)
		fc_parameterized_paths = lcons(fc_cheapest_total_path, fc_parameterized_paths);

	/*
	 * 如果没有未参数化路径，使用最佳参数化路径作为
	 * cheapest_total_path（但不作为 cheapest_startup_path）。
	 */
	if (fc_cheapest_total_path == NULL)
		fc_cheapest_total_path = fc_best_param_path;
	Assert(fc_cheapest_total_path != NULL);

	fc_parent_rel->cheapest_startup_path = fc_cheapest_startup_path;
	fc_parent_rel->cheapest_total_path = fc_cheapest_total_path;
	fc_parent_rel->cheapest_unique_path = NULL;	/* 仅在需要时计算 */ 
	fc_parent_rel->cheapest_parameterized_paths = fc_parameterized_paths;
}


/*
 * add_path
 *	  考虑指定父关系的潜在实现路径，
 *	  并在路径列表中添加该路径，如果它值得考虑的话。
 *	  如果一个路径具有更好的排序顺序（更好的路径键）或
 *	  更便宜的成本（在任何维度上），或者生成的行数少于任何
 *	  现有路径，并且这些路径具有相同或超集的参数化关系，
 *	  则该路径是值得的。
 *	  我们还认为并行安全的路径比其他路径更具价值。
 *
 *	  我们还会从关系的路径列表中删除任何被 new_path 主导的旧路径——
 *	  即，new_path 更便宜，排序至少同样良好，
 *	  生成的行数不多，需要的外部关系不比旧路径多，
 *	  且并行安全性不低。
 *
 *	  在大多数情况下，具有超集参数化的路径会生成较少的行
 *	  （因为它有更多的连接条件要应用），因此这两个优点会 
 *	  朝相反的方向移动；这意味着一种参数化的路径往往很少会
 *	  主导另一种路径。但此类情况确实会出现，所以我们仍会进行
 *	  全部检查。
 *
 *	  此函数中嵌入了两个政策决策，以及其兄弟函数 add_path_precheck。
 *	  首先，我们将所有参数化路径视为具有 NIL pathkeys，
 *	  这样它们就不能在排序顺序的基础上获胜比较。
 *	  这是为了减少保留的参数化路径的数量；请参阅 src/backend/optimizer/README 中的讨论。
 *
 *	  其次，我们仅在未参数化路径的 parent_rel->consider_startup 为 true，
 *	  或在参数化路径的 parent_rel->consider_param_startup 为 true 的情况下，
 *	  才认为便宜的启动成本具有趣味性。
 *	  同样，这允许我们更早地丢弃无用的路径。
 *
 *	  路径列表按总成本排序，较便宜的路径位于前面。
 *	  在此例程中，这仅仅是一个速度优化：
 *	  这样做使我们在几次比较后拒绝一个劣质路径的可能性更高，
 *	  而不是需要进行多次比较。
 *	  然而，add_path_precheck 依赖于这种顺序，在可能的情况下提前退出。
 *
 *	  注意：丢弃的路径对象会立即进行 pfree，以减少规划器
 *	  的内存消耗。我们不敢尝试释放路径的子结构，
 *	  因为其中很多可能与其他路径或查询树本身共享；
 *	  但是，仅回收被丢弃的路径节点在大连接树中是非常有用的节省。
 *	  我们也可以回收路径列表的 List 节点。
 *
 *	  正如优化器/README 中所述，删除先前接受的路径是
 *	  安全的，因为我们知道这个关系的路径尚未能被任何
 *	  其他关系引用，例如更高层的连接。
 *	  然而，在某些情况下，某路径可能被另一个路径为
 *	  其自身关系所引用；即使它被新路径主导，我们也不能删除
 *	  这样的路径。目前这种情况仅发生在 IndexPath 对象，
 *	  它们可能被 BitmapHeapPaths 作为子路径引用，也作为
 *	  自身的路径。因此，在拒绝 IndexPaths 时我们不进行 pfree。
 *
 * 'parent_rel' 是路径对应的关系条目。
 * 'new_path' 是 parent_rel 的潜在路径。
 *
 * 不返回任何内容，但会修改 parent_rel->pathlist。
 */
void add_path(RelOptInfo *fc_parent_rel, Path *fc_new_path)
{
	bool		fc_accept_new = true;	/* 除非我们找到一个更优的旧路径 */
	int			insert_at = 0;	/* 新项的插入位置 */
	List	   *fc_new_path_pathkeys;
	ListCell   *p1;

	/*
	 * 这是检查查询取消的一个便利位置——规划器没有很长时间
	 * 不调用 add_path()。
	 */
	CHECK_FOR_INTERRUPTS();

	/* 假装参数化路径没有路径键，如上面评论所述 */
	fc_new_path_pathkeys = fc_new_path->param_info ? NIL : fc_new_path->pathkeys;

	/*
	 * 循环检查提议的新路径与旧路径的比较。请注意，有可能
	 * 因为新路径主导旧路径而同时抛弃多个旧路径。
	 */
	foreach(p1, fc_parent_rel->pathlist)
	{
		Path	   *fc_old_path = (Path *) lfirst(p1);
		bool		fc_remove_old = false; /* 除非新的被证明是优越的 */
		PathCostComparison fc_costcmp;
		PathKeysComparison fc_keyscmp;
		BMS_Comparison fc_outercmp;

		/*
		 * 进行模糊成本比较，使用标准的模糊限制。
		 */
		fc_costcmp = fc_compare_path_costs_fuzzily(fc_new_path, fc_old_path,
											 STD_FUZZ_FACTOR);

		/*
		 * 如果两条路径在启动成本和总成本的比较上不同，
		 * 那么我们想保留这两条路径，并且可以跳过比较路径键和
		 * 所需的外部关系。如果它们的比较相同，则继续进行
		 * 其他比较。行数检查最后进行。（我们以此顺序进行测试，
		 * 因为成本比较最有可能得出“不同”的结果，而路径键比较下一个最有可能。正如上面解释的那样，行数很少
		 * 会产生差异，因此即使比较便宜也没有多大意义在
		 * 早些时候进行检查。）
		 */
		if (fc_costcmp != COSTS_DIFFERENT)
		{
			/* 类似地检查是否任一方在路径键上主导 */
			List	   *fc_old_path_pathkeys;

			fc_old_path_pathkeys = fc_old_path->param_info ? NIL : fc_old_path->pathkeys;
			fc_keyscmp = compare_pathkeys(fc_new_path_pathkeys,
									   fc_old_path_pathkeys);
			if (fc_keyscmp != PATHKEYS_DIFFERENT)
			{
				switch (fc_costcmp)
				{
					case COSTS_EQUAL:
						fc_outercmp = bms_subset_compare(PATH_REQ_OUTER(fc_new_path),
													  PATH_REQ_OUTER(fc_old_path));
						if (fc_keyscmp == PATHKEYS_BETTER1)
						{
							if ((fc_outercmp == BMS_EQUAL ||
								 fc_outercmp == BMS_SUBSET1) &&
								fc_new_path->rows <= fc_old_path->rows &&
								fc_new_path->parallel_safe >= fc_old_path->parallel_safe)
								fc_remove_old = true;	/* 新的主导旧的 */
						}
						else if (fc_keyscmp == PATHKEYS_BETTER2)
						{
							if ((fc_outercmp == BMS_EQUAL ||
								 fc_outercmp == BMS_SUBSET2) &&
								fc_new_path->rows >= fc_old_path->rows &&
								fc_new_path->parallel_safe <= fc_old_path->parallel_safe)
								fc_accept_new = false; /* 旧的主导新的 */
						}
						else	/* keyscmp == PATHKEYS_EQUAL */
						{
							if (fc_outercmp == BMS_EQUAL)
							{
								/*
								 * 路径键和外部关系相同，并且模糊上
								 * 成本也相同，因此只保留一个；决定
								 * 哪一个时，先检查并行安全性，然后
								 * 行数，再进行模糊成本比较，
								 * 使用非常小的模糊限制。（我们曾经做过
								 * 精确成本比较，但这会导致
								 * 由于成本估算的舍入而产生烦人的平台特定计划变体。）如果
								 * 事情仍然平局，任意保留
								 * 仅旧路径。请注意，即使
								 * 不那么模糊的比较决定启动
								 * 和总成本的比较不同，我们也将
								 * 仅保留旧路径。
								 */
								if (fc_new_path->parallel_safe >
									fc_old_path->parallel_safe)
									fc_remove_old = true;	/* 新的主导旧的 */
								else if (fc_new_path->parallel_safe <
										 fc_old_path->parallel_safe)
									fc_accept_new = false; /* 旧的主导新的 */
								else if (fc_new_path->rows < fc_old_path->rows)
									fc_remove_old = true;	/* 新的主导旧的 */
								else if (fc_new_path->rows > fc_old_path->rows)
									fc_accept_new = false; /* 旧的主导新的 */
								else if (fc_compare_path_costs_fuzzily(fc_new_path,
																	fc_old_path,
																	1.0000000001) == COSTS_BETTER1)
									fc_remove_old = true;	/* 新的主导旧的 */
								else
									fc_accept_new = false; /* 旧的等于或
														 * 主导新的 */
							}
							else if (fc_outercmp == BMS_SUBSET1 &&
									 fc_new_path->rows <= fc_old_path->rows &&
									 fc_new_path->parallel_safe >= fc_old_path->parallel_safe)
								fc_remove_old = true;	/* 新的主导旧的 */
							else if (fc_outercmp == BMS_SUBSET2 &&
									 fc_new_path->rows >= fc_old_path->rows &&
									 fc_new_path->parallel_safe <= fc_old_path->parallel_safe)
								fc_accept_new = false; /* 旧的主导新的 */
							/* 否则不同的参数化，保留两者 */
						}
						break;
					case COSTS_BETTER1:
						if (fc_keyscmp != PATHKEYS_BETTER2)
						{
							fc_outercmp = bms_subset_compare(PATH_REQ_OUTER(fc_new_path),
														  PATH_REQ_OUTER(fc_old_path));
							if ((fc_outercmp == BMS_EQUAL ||
								 fc_outercmp == BMS_SUBSET1) &&
								fc_new_path->rows <= fc_old_path->rows &&
								fc_new_path->parallel_safe >= fc_old_path->parallel_safe)
								fc_remove_old = true;	/* 新的主导旧的 */
						}
						break;
					case COSTS_BETTER2:
						if (fc_keyscmp != PATHKEYS_BETTER1)
						{
							fc_outercmp = bms_subset_compare(PATH_REQ_OUTER(fc_new_path),
														  PATH_REQ_OUTER(fc_old_path));
							if ((fc_outercmp == BMS_EQUAL ||
								 fc_outercmp == BMS_SUBSET2) &&
								fc_new_path->rows >= fc_old_path->rows &&
								fc_new_path->parallel_safe <= fc_old_path->parallel_safe)
								fc_accept_new = false; /* 旧的主导新的 */
						}
						break;
					case COSTS_DIFFERENT:

						/*
						 * 不能到这里，但保留此情况以保持编译器
						 * 安静
						 */
						break;
				}
			}
		}

		/*
		 * 如果被新的路径主导，则从路径列表中移除当前元素。
		 */
		if (fc_remove_old)
		{
			fc_parent_rel->pathlist = foreach_delete_current(fc_parent_rel->pathlist,
														  p1);

			/*
			 * 如果可能，删除被删除单元指向的数据
			 */
			if (!IsA(fc_old_path, IndexPath))
				pfree(fc_old_path);
		}
		else
		{
			/* 如果新路径的成本 >= 旧路径，则新路径位于此旧路径之后 */
			if (fc_new_path->total_cost >= fc_old_path->total_cost)
				insert_at = foreach_current_index(p1) + 1;
		}

		/*
		 * 如果我们找到一个主导新路径的旧路径，我们可以退出
		 * 扫描路径列表；我们不会添加新路径，假设
		 * 新路径不能主导路径列表中的任何其他元素。
		 */
		if (!fc_accept_new)
			break;
	}

	if (fc_accept_new)
	{
		/* 接受新路径：在路径列表中插入到合适的位置 */
		fc_parent_rel->pathlist =
			list_insert_nth(fc_parent_rel->pathlist, insert_at, fc_new_path);
	}
	else
	{
		/* 拒绝并回收新路径 */
		if (!IsA(fc_new_path, IndexPath))
			pfree(fc_new_path);
	}
}

/*
 * add_path_precheck
 *	  检查提议的新路径是否可能被接受。
 *	  我们假设我们准确地知道路径的路径键和参数化，并且
 *	  具有其成本的下界。
 *
 * 请注意，我们不知道路径的行数，因为在预检查之前
 * 获取其估算值太昂贵。我们在这里假设
 * 超集参数化的路径将生成更少的行；如果成立，
 * 那么具有不同参数化的路径不能相互主导，
 * 因此我们可以简单地忽略其他参数化的现有路径。
 * （在这种经验法则失败的少数情况下，add_path将
 * 去掉劣势路径。）
 *
 * 当调用此方法时，我们其实还没有构建一个路径结构，
 * 所以所需的信息必须逐渐传递。
 */
bool add_path_precheck(RelOptInfo *fc_parent_rel,
				  Cost fc_startup_cost, Cost fc_total_cost,
				  List *fc_pathkeys, Relids fc_required_outer)
{
	List	   *fc_new_path_pathkeys;
	bool		fc_consider_startup;
	ListCell   *fc_p1;

	/* 假装参数化路径没有路径键，符合 add_path 政策 */
	fc_new_path_pathkeys = fc_required_outer ? NIL : fc_pathkeys;

	/* 决定新路径的启动成本是否有趣 */
	fc_consider_startup = fc_required_outer ? fc_parent_rel->consider_param_startup : fc_parent_rel->consider_startup;

	foreach(fc_p1, fc_parent_rel->pathlist)
	{
		Path	   *fc_old_path = (Path *) lfirst(fc_p1);
		PathKeysComparison fc_keyscmp;

		/*
		 * 我们正在寻找一条具有相同参数化（并且假设具有相同行数）的旧路径，
		 * 它在路径键以及两个成本度量上主导新路径。
		 * 如果我们找到一条，我们可以拒绝新路径。
		 *
		 * 这里的成本比较应该与 compare_path_costs_fuzzily 一致。
		 */
		if (fc_total_cost > fc_old_path->total_cost * STD_FUZZ_FACTOR)
		{
			/* 新路径仅在考虑启动时成本才能获胜 */
			if (fc_startup_cost > fc_old_path->startup_cost * STD_FUZZ_FACTOR ||
				!fc_consider_startup)
			{
				/* 新路径在成本上输，因此检查路径键... */
				List	   *fc_old_path_pathkeys;

				fc_old_path_pathkeys = fc_old_path->param_info ? NIL : fc_old_path->pathkeys;
				fc_keyscmp = compare_pathkeys(fc_new_path_pathkeys,
										   fc_old_path_pathkeys);
				if (fc_keyscmp == PATHKEYS_EQUAL ||
					fc_keyscmp == PATHKEYS_BETTER2)
				{
					/* 新路径在路径键上没有获胜... */
					if (bms_equal(fc_required_outer, PATH_REQ_OUTER(fc_old_path)))
					{
						/* 找到一个主导新路径的旧路径 */
						return false;
					}
				}
			}
		}
		else
		{
			/*
			 * 由于路径列表是按总成本排序的，一旦我们找到一个总成本大于新路径的路径，就可以停止查找。
			 */
			break;
		}
	}

	return true;
}

/*
 * add_partial_path
 *	  与 add_path 类似，我们在这里的目标是考虑一个路径是否值得保留，但考虑因素略有不同。
 *	  部分路径是指可以在任意数量的并行工作者中执行的路径，以便每个工作者生成该路径整体结果的一个子集。
 *
 *	  如同 add_path，partial_pathlist 保持按最便宜的总路径在前排序。这被多个地方依赖，
 *	  它们只需将前面的条目视为最便宜的路径，而无需搜索。
 *
 *	  我们不会生成参数化的部分路径，原因有几种。最重要的是，它们不安全执行，因为没有任何东西可以确保
 *	  在计划的参数化部分中的并行扫描在每个工作者中同时运行相同的值。
 *	  幸运的是，这似乎不太可能值得，因为让每个工作者扫描整个外部关系和一个内部关系的子集
 *	  通常都是一个糟糕的计划。计划的内部（参数化）部分无论如何都将很小。可能会有一些罕见的情况
 *	  在这种情况下会有很大的收益——例如，如果连接顺序约束将一个一行关系放在顶级连接的外部，
 *	  而内部侧是一个参数化计划——但我们将不得不满足于在
 *	  有人构建能够应对这些情况的执行器基础结构之前不处理此类情况。
 *
 *	  因为我们在这里不考虑参数化路径，所以我们也不需要将行数作为质量的衡量标准：每个路径将
 *	  产生相同数量的行。我们也无需考虑启动成本：并行性仅用于将要完成的计划。
 *	  因此，该例程比 add_path 简单得多：它只需考虑路径键和总成本。
 *
 *	  与 add_path 类似，我们会 pfree 被发现被另一个部分路径主导的路径；这要求对此类路径尚没有其他引用。
 *	  因此，在我们完成为其创建所有部分路径之前，GatherPaths 不能为一个 rel 创建。
 *	  与 add_path 不同，我们不以 IndexPaths 为例，因为部分索引路径不会被部分 BitmapHeapPaths 引用。
 */
void add_partial_path(RelOptInfo *fc_parent_rel, Path *fc_new_path)
{
	bool		fc_accept_new = true;	/* 除非我们找到一个更优的旧路径 */
	int			insert_at = 0;	/* 新项的插入位置 */
	ListCell   *p1;

	/* 检查查询取消。 */
	CHECK_FOR_INTERRUPTS();

	/* 要添加的路径必须是并行安全的。 */
	Assert(fc_new_path->parallel_safe);

	/* 关系也应该适合并行处理。 */
	Assert(fc_parent_rel->consider_parallel);

	/*
	 * 如同 add_path，对被新路径主导的任何路径进行抛弃，
	 * 但如果某些现有路径主导了新路径，则抛弃新路径。
	 */
	foreach(p1, fc_parent_rel->partial_pathlist)
	{
		Path	   *fc_old_path = (Path *) lfirst(p1);
		bool		fc_remove_old = false; /* 除非新的被证明是优越的 */
		PathKeysComparison fc_keyscmp;

		/* 比较路径键。 */
		fc_keyscmp = compare_pathkeys(fc_new_path->pathkeys, fc_old_path->pathkeys);

		/* 除非路径键不兼容，否则只保留两个路径中的一个。 */
		if (fc_keyscmp != PATHKEYS_DIFFERENT)
		{
			if (fc_new_path->total_cost > fc_old_path->total_cost * STD_FUZZ_FACTOR)
			{
				/* 新路径成本更高；仅在路径键更好时保留。 */
				if (fc_keyscmp != PATHKEYS_BETTER1)
					fc_accept_new = false;
			}
			else if (fc_old_path->total_cost > fc_new_path->total_cost
					 * STD_FUZZ_FACTOR)
			{
				/* 旧路径成本更高；仅在路径键更好时保留。 */
				if (fc_keyscmp != PATHKEYS_BETTER2)
					fc_remove_old = true;
			}
			else if (fc_keyscmp == PATHKEYS_BETTER1)
			{
				/* 成本大致相同，新路径的路径键更好。 */
				fc_remove_old = true;
			}
			else if (fc_keyscmp == PATHKEYS_BETTER2)
			{
				/* 成本大致相同，旧路径的路径键更好。 */
				fc_accept_new = false;
			}
			else if (fc_old_path->total_cost > fc_new_path->total_cost * 1.0000000001)
			{
				/* 路径键相同，旧路径成本更高。 */
				fc_remove_old = true;
			}
			else
			{
				/*
				 * 路径键相同，新路径没有显著更便宜。
				 */
				fc_accept_new = false;
			}
		}

		/*
		 * 如果被新路径主导，则从 partial_pathlist 中移除当前元素。
		 */
		if (fc_remove_old)
		{
			fc_parent_rel->partial_pathlist =
				foreach_delete_current(fc_parent_rel->partial_pathlist, p1);
			pfree(fc_old_path);
		}
		else
		{
			/* 如果新路径的成本 >= 旧路径，则新路径位于此旧路径之后 */
			if (fc_new_path->total_cost >= fc_old_path->total_cost)
				insert_at = foreach_current_index(p1) + 1;
		}

		/*
		 * 如果我们找到的旧路径主导了 new_path，我们可以停止扫描 partial_pathlist；
		 * 我们将不添加 new_path，并假设 new_path 不能主导任何后续路径。
		 */
		if (!fc_accept_new)
			break;
	}

	if (fc_accept_new)
	{
		/* 接受新路径：在适当的位置插入。 */
		fc_parent_rel->partial_pathlist =
			list_insert_nth(fc_parent_rel->partial_pathlist, insert_at, fc_new_path);
	}
	else
	{
		/* 拒绝并回收新路径 */
		pfree(fc_new_path);
	}
}

/*
 * add_partial_path_precheck
 *	  检查建议的新部分路径是否可能被接受。
 *
 * 与 add_path_precheck 不同，我们可以忽略启动成本和参数化，
 * 因为它们对于部分路径无关紧要（见 add_partial_path）。但我们确实希望确保
 * 如果已有主导它的完整路径，就不会添加部分路径，因为在此情况下，建议的路径肯定是失败者。
 */
bool add_partial_path_precheck(RelOptInfo *fc_parent_rel, Cost fc_total_cost,
						  List *fc_pathkeys)
{
	ListCell   *fc_p1;

	/*
	 * 我们在这里的目标有两个。首先，我们希望找出这条路径是否明显不如某个现有部分路径。
	 * 如果是这样，我们希望立即拒绝它。第二，我们希望找出这条路径是否明显优于某个现有部分路径——
	 * 至少是恢复最终成本计算之后的情况。如果是这样，我们绝对希望考虑它。
	 *
	 * 与 add_path() 不同，我们在这里始终比较路径键。这是因为我们
	 * 预计 partial_pathlist 非常短，并且在此阶段获取明确答案避免了调用 add_path_precheck 的需要。
	 */
	foreach(fc_p1, fc_parent_rel->partial_pathlist)
	{
		Path	   *fc_old_path = (Path *) lfirst(fc_p1);
		PathKeysComparison fc_keyscmp;

		fc_keyscmp = compare_pathkeys(fc_pathkeys, fc_old_path->pathkeys);
		if (fc_keyscmp != PATHKEYS_DIFFERENT)
		{
			if (fc_total_cost > fc_old_path->total_cost * STD_FUZZ_FACTOR &&
				fc_keyscmp != PATHKEYS_BETTER1)
				return false;
			if (fc_old_path->total_cost > fc_total_cost * STD_FUZZ_FACTOR &&
				fc_keyscmp != PATHKEYS_BETTER2)
				return true;
		}
	}

	
/*
	 * 这个路径既不明显劣于现有部分路径，也不明显好到可以替换一个。与非并行计划进行比较。如果在考虑到Gather节点的成本之前它就已经失败，我们应该毫无疑问地拒绝它。
	 *
	 * 注意，我们将total_cost传递给add_path_precheck两次。这是因为考虑部分路径的启动成本是没有好处的；如果并行运行，最终的计划将会完成执行。
	 */
	if (!add_path_precheck(fc_parent_rel, fc_total_cost, fc_total_cost, fc_pathkeys,
						   NULL))
		return false;

	return true;
}


/*****************************************************************************
 *		路径节点创建例程
 *****************************************************************************/

/*
 * create_seqscan_path
 *	  创建一个对应于顺序扫描的路径，返回路径节点。
 */
Path * create_seqscan_path(PlannerInfo *fc_root, RelOptInfo *fc_rel,
					Relids fc_required_outer, int fc_parallel_workers)
{
	Path	   *fc_pathnode = makeNode(Path);

	fc_pathnode->pathtype = T_SeqScan;
	fc_pathnode->parent = fc_rel;
	fc_pathnode->pathtarget = fc_rel->reltarget;
	fc_pathnode->param_info = get_baserel_parampathinfo(fc_root, fc_rel,
													 fc_required_outer);
	fc_pathnode->parallel_aware = (fc_parallel_workers > 0);
	fc_pathnode->parallel_safe = fc_rel->consider_parallel;
	fc_pathnode->parallel_workers = fc_parallel_workers;
	fc_pathnode->pathkeys = NIL;	/* seqscan 的结果是无序的 */

	cost_seqscan(fc_pathnode, fc_root, fc_rel, fc_pathnode->param_info);

	return fc_pathnode;
}

/*
 * create_samplescan_path
 *	  为采样表扫描创建一个路径节点。
 */
Path * create_samplescan_path(PlannerInfo *fc_root, RelOptInfo *fc_rel, Relids fc_required_outer)
{
	Path	   *fc_pathnode = makeNode(Path);

	fc_pathnode->pathtype = T_SampleScan;
	fc_pathnode->parent = fc_rel;
	fc_pathnode->pathtarget = fc_rel->reltarget;
	fc_pathnode->param_info = get_baserel_parampathinfo(fc_root, fc_rel,
													 fc_required_outer);
	fc_pathnode->parallel_aware = false;
	fc_pathnode->parallel_safe = fc_rel->consider_parallel;
	fc_pathnode->parallel_workers = 0;
	fc_pathnode->pathkeys = NIL;	/* samplescan 的结果是无序的 */

	cost_samplescan(fc_pathnode, fc_root, fc_rel, fc_pathnode->param_info);

	return fc_pathnode;
}

/*
 * create_index_path
 *	  创建一个索引扫描的路径节点。
 *
 * 'index' 是一个可用的索引。
 * 'indexclauses' 是一个IndexClause节点的列表，表示在扫描中要强制执行的条件。
 * 'indexorderbys' 是一个裸表达式的列表（没有RestrictInfos），
 *		 用作扫描中的索引排序运算符。
 * 'indexorderbycols' 是一个整数列表，表示可以与排序运算符一起使用的索引列编号（从零开始）。
 * 'pathkeys' 描述了路径的排序。
 * 'indexscandir' 是一个有序索引的ForwardScanDirection或BackwardScanDirection，
 *		 或无序索引的NoMovementScanDirection。
 * 'indexonly' 如果想要一个索引仅扫描则为true。
 * 'required_outer' 是参数化路径的外部relids集合。
 * 'loop_count' 是索引扫描重复的次数，用于估计缓存行为。
 * 'partial_path' 如果构造并行索引扫描路径则为true。
 *
 * 返回新的路径节点。
 */
IndexPath *
create_index_path(PlannerInfo *fc_root,
				  IndexOptInfo *fc_index,
				  List *fc_indexclauses,
				  List *fc_indexorderbys,
				  List *fc_indexorderbycols,
				  List *fc_pathkeys,
				  ScanDirection fc_indexscandir,
				  bool fc_indexonly,
				  Relids fc_required_outer,
				  double fc_loop_count,
				  bool fc_partial_path)
{
	IndexPath  *fc_pathnode = makeNode(IndexPath);
	RelOptInfo *fc_rel = fc_index->rel;

	fc_pathnode->path.pathtype = fc_indexonly ? T_IndexOnlyScan : T_IndexScan;
	fc_pathnode->path.parent = fc_rel;
	fc_pathnode->path.pathtarget = fc_rel->reltarget;
	fc_pathnode->path.param_info = get_baserel_parampathinfo(fc_root, fc_rel,
														  fc_required_outer);
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel;
	fc_pathnode->path.parallel_workers = 0;
	fc_pathnode->path.pathkeys = fc_pathkeys;

	fc_pathnode->indexinfo = fc_index;
	fc_pathnode->indexclauses = fc_indexclauses;
	fc_pathnode->indexorderbys = fc_indexorderbys;
	fc_pathnode->indexorderbycols = fc_indexorderbycols;
	fc_pathnode->indexscandir = fc_indexscandir;

	cost_index(fc_pathnode, fc_root, fc_loop_count, fc_partial_path);

	return fc_pathnode;
}

/*
 * create_bitmap_heap_path
 *	  创建一个位图扫描的路径节点。
 *
 * 'bitmapqual' 是一个IndexPath、BitmapAndPath和BitmapOrPath节点的树。
 * 'required_outer' 是参数化路径的外部relids集合。
 * 'loop_count' 是索引扫描重复的次数，用于估计缓存行为。
 *
 * loop_count 应与创建组件IndexPaths时使用的值匹配。
 */
BitmapHeapPath *
create_bitmap_heap_path(PlannerInfo *fc_root,
						RelOptInfo *fc_rel,
						Path *fc_bitmapqual,
						Relids fc_required_outer,
						double fc_loop_count,
						int fc_parallel_degree)
{
	BitmapHeapPath *fc_pathnode = makeNode(BitmapHeapPath);

	fc_pathnode->path.pathtype = T_BitmapHeapScan;
	fc_pathnode->path.parent = fc_rel;
	fc_pathnode->path.pathtarget = fc_rel->reltarget;
	fc_pathnode->path.param_info = get_baserel_parampathinfo(fc_root, fc_rel,
														  fc_required_outer);
	fc_pathnode->path.parallel_aware = (fc_parallel_degree > 0);
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel;
	fc_pathnode->path.parallel_workers = fc_parallel_degree;
	fc_pathnode->path.pathkeys = NIL;	/* 始终无序 */

	fc_pathnode->bitmapqual = fc_bitmapqual;

	cost_bitmap_heap_scan(&fc_pathnode->path, fc_root, fc_rel,
						  fc_pathnode->path.param_info,
						  fc_bitmapqual, fc_loop_count);

	return fc_pathnode;
}

/*
 * create_bitmap_and_path
 *	  创建一个表示BitmapAnd的路径节点。
 */
BitmapAndPath *
create_bitmap_and_path(PlannerInfo *fc_root,
					   RelOptInfo *fc_rel,
					   List *fc_bitmapquals)
{
	BitmapAndPath *fc_pathnode = makeNode(BitmapAndPath);
	Relids		fc_required_outer = NULL;
	ListCell   *fc_lc;

	fc_pathnode->path.pathtype = T_BitmapAnd;
	fc_pathnode->path.parent = fc_rel;
	fc_pathnode->path.pathtarget = fc_rel->reltarget;

	/*
	 * 将需要的外部关系识别为子路径所依赖的集合的并集。 （或者，我们可以坚持要求调用者传入这个，但在这里计算更方便且可靠。）
	 */
	foreach(fc_lc, fc_bitmapquals)
	{
		Path	   *fc_bitmapqual = (Path *) lfirst(fc_lc);

		fc_required_outer = bms_add_members(fc_required_outer,
										 PATH_REQ_OUTER(fc_bitmapqual));
	}
	fc_pathnode->path.param_info = get_baserel_parampathinfo(fc_root, fc_rel,
														  fc_required_outer);

	/*
	 * 目前，BitmapHeapPath、BitmapAndPath或BitmapOrPath将仅在rel->consider_parallel被设置时才是并行安全的。因此，我们可以仅基于关系级别的标志设置此路径的标志，而无需实际迭代子节点列表。
	 */
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel;
	fc_pathnode->path.parallel_workers = 0;

	fc_pathnode->path.pathkeys = NIL;	/* 始终无序 */

	fc_pathnode->bitmapquals = fc_bitmapquals;

	/* 这同时设置了bitmapselectivity和常规成本字段： */
	cost_bitmap_and_node(fc_pathnode, fc_root);

	return fc_pathnode;
}

/*
 * create_bitmap_or_path
 *	  创建一个表示BitmapOr的路径节点。
 */
BitmapOrPath *
create_bitmap_or_path(PlannerInfo *fc_root,
					  RelOptInfo *fc_rel,
					  List *fc_bitmapquals)
{
	BitmapOrPath *fc_pathnode = makeNode(BitmapOrPath);
	Relids		fc_required_outer = NULL;
	ListCell   *fc_lc;

	fc_pathnode->path.pathtype = T_BitmapOr;
	fc_pathnode->path.parent = fc_rel;
	fc_pathnode->path.pathtarget = fc_rel->reltarget;

	/*
	 * 将需要的外部关系识别为子路径所依赖的集合的并集。 （或者，我们可以坚持要求调用者传入这个，但在这里计算更方便且可靠。）
	 */
	foreach(fc_lc, fc_bitmapquals)
	{
		Path	   *fc_bitmapqual = (Path *) lfirst(fc_lc);

		fc_required_outer = bms_add_members(fc_required_outer,
										 PATH_REQ_OUTER(fc_bitmapqual));
	}
	fc_pathnode->path.param_info = get_baserel_parampathinfo(fc_root, fc_rel,
														  fc_required_outer);

	/*
	 * 目前，BitmapHeapPath、BitmapAndPath或BitmapOrPath将仅在rel->consider_parallel被设置时才是并行安全的。因此，我们可以仅基于关系级别的标志设置此路径的标志，而无需实际迭代子节点列表。
	 */
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel;
	fc_pathnode->path.parallel_workers = 0;

	fc_pathnode->path.pathkeys = NIL;	/* 始终无序 */

	fc_pathnode->bitmapquals = fc_bitmapquals;

	/* 这同时设置了bitmapselectivity和常规成本字段： */
	cost_bitmap_or_node(fc_pathnode, fc_root);

	return fc_pathnode;
}

/*
 * create_tidscan_path
 *	  创建一个对应于TID扫描的路径，并返回路径节点。
 */
TidPath *
create_tidscan_path(PlannerInfo *fc_root, RelOptInfo *fc_rel, List *fc_tidquals,
					Relids fc_required_outer)
{
	TidPath    *fc_pathnode = makeNode(TidPath);

	fc_pathnode->path.pathtype = T_TidScan;
	fc_pathnode->path.parent = fc_rel;
	fc_pathnode->path.pathtarget = fc_rel->reltarget;
	fc_pathnode->path.param_info = get_baserel_parampathinfo(fc_root, fc_rel,
														  fc_required_outer);
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel;
	fc_pathnode->path.parallel_workers = 0;
	fc_pathnode->path.pathkeys = NIL;	/* 始终无序 */

	fc_pathnode->tidquals = fc_tidquals;

	cost_tidscan(&fc_pathnode->path, fc_root, fc_rel, fc_tidquals,
				 fc_pathnode->path.param_info);

	return fc_pathnode;
}

/*
 * create_tidrangescan_path
 *	  创建一个对应于TID范围扫描的路径，并返回路径节点。
 */
TidRangePath *
create_tidrangescan_path(PlannerInfo *fc_root, RelOptInfo *fc_rel,
						 List *fc_tidrangequals, Relids fc_required_outer)
{
	TidRangePath *fc_pathnode = makeNode(TidRangePath);

	fc_pathnode->path.pathtype = T_TidRangeScan;
	fc_pathnode->path.parent = fc_rel;
	fc_pathnode->path.pathtarget = fc_rel->reltarget;
	fc_pathnode->path.param_info = get_baserel_parampathinfo(fc_root, fc_rel,
														  fc_required_outer);
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel;
	fc_pathnode->path.parallel_workers = 0;
	fc_pathnode->path.pathkeys = NIL;	/* 始终无序 */

	fc_pathnode->tidrangequals = fc_tidrangequals;

	cost_tidrangescan(&fc_pathnode->path, fc_root, fc_rel, fc_tidrangequals,
					  fc_pathnode->path.param_info);

	return fc_pathnode;
}

/*
 * create_append_path
 *	  创建一个对应于Append计划的路径，并返回路径节点。
 *
 * 请注意，我们必须处理subpaths = NIL，这表示一个虚拟访问路径。
 * 此外，有调用者传递root = NULL。
 */
AppendPath *
create_append_path(PlannerInfo *fc_root,
				   RelOptInfo *fc_rel,
				   List *fc_subpaths, List *fc_partial_subpaths,
				   List *fc_pathkeys, Relids fc_required_outer,
				   int fc_parallel_workers, bool fc_parallel_aware,
				   double fc_rows)
{
	AppendPath *fc_pathnode = makeNode(AppendPath);
	ListCell   *fc_l;

	Assert(!fc_parallel_aware || fc_parallel_workers > 0);

	fc_pathnode->path.pathtype = T_Append;
	fc_pathnode->path.parent = fc_rel;
	fc_pathnode->path.pathtarget = fc_rel->reltarget;

	/*
	 * 在为分区表生成Append路径时，可能会有用于运行时剪枝的参数化quals。因此，
	 * 以与任何其他baserel相同的方式计算path.param_info，以便
	 * 这些quals将在make_partition_pruneinfo()中可用。 （这个
	 * 在非baserel（即在非叶子子分区上扫描）上正常工作是不合适的，在这种情况下也没有必要。如果没有"root"，也必须跳过它。）
	 */
	if (fc_root && fc_rel->reloptkind == RELOPT_BASEREL && IS_PARTITIONED_REL(fc_rel))
		fc_pathnode->path.param_info = get_baserel_parampathinfo(fc_root,
															  fc_rel,
															  fc_required_outer);
	else
		fc_pathnode->path.param_info = get_appendrel_parampathinfo(fc_rel,
																fc_required_outer);

	fc_pathnode->path.parallel_aware = fc_parallel_aware;
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel;
	fc_pathnode->path.parallel_workers = fc_parallel_workers;
	fc_pathnode->path.pathkeys = fc_pathkeys;

	/*
	 * 对于并行append，非部分路径按总成本降序排序。这样，完成所有非部分路径的总时间就会
	 * 最小化。此外，部分路径按启动成本降序排序。可能有一些路径需要由
	 * 单个工作线程进行启动工作。在这种情况下，最好先让工人选择昂贵的路径，而领导者应选择
	 * 成本最低的启动计划。
	 */
	if (fc_pathnode->path.parallel_aware)
	{
		/*
		 * 在Append有pathkeys时，我们不能干扰子路径的顺序。它们的顺序对保持pathkeys有效至关重要。
		 */
		Assert(fc_pathkeys == NIL);

		list_sort(fc_subpaths, fc_append_total_cost_compare);
		list_sort(fc_partial_subpaths, fc_append_startup_cost_compare);
	}
	fc_pathnode->first_partial_path = list_length(fc_subpaths);
	fc_pathnode->subpaths = list_concat(fc_subpaths, fc_partial_subpaths);

	/*
	 * 如果已知且路径是唯一基础关系，请应用查询范围内的LIMIT。
	 * （在这个低级别处理这个有点笨拙。）
	 */
	if (fc_root != NULL && bms_equal(fc_rel->relids, fc_root->all_baserels))
		fc_pathnode->limit_tuples = fc_root->limit_tuples;
	else
		fc_pathnode->limit_tuples = -1.0;

	foreach(fc_l, fc_pathnode->subpaths)
	{
		Path	   *fc_subpath = (Path *) lfirst(fc_l);

		fc_pathnode->path.parallel_safe = fc_pathnode->path.parallel_safe &&
			fc_subpath->parallel_safe;

		/* 所有子路径必须具有相同的参数化 */
		Assert(bms_equal(PATH_REQ_OUTER(fc_subpath), fc_required_outer));
	}

	Assert(!fc_parallel_aware || fc_pathnode->path.parallel_safe);

	/*
	 * 如果只有一个子路径，Append就是一个无操作，并将在稍后被丢弃（在setrefs.c中）；因此，我们可以继承子路径的
	 * 大小和成本，以及其pathkeys（覆盖调用者可能指定的任何内容）。否则，我们必须进行正常的costsize
	 * 计算。
	 */
	if (list_length(fc_pathnode->subpaths) == 1)
	{
		Path	   *fc_child = (Path *) linitial(fc_pathnode->subpaths);

		fc_pathnode->path.rows = fc_child->rows;
		fc_pathnode->path.startup_cost = fc_child->startup_cost;
		fc_pathnode->path.total_cost = fc_child->total_cost;
		fc_pathnode->path.pathkeys = fc_child->pathkeys;
	}
	else
		cost_append(fc_pathnode);

	/* 如果调用者提供了行估计，则覆盖计算值。 */
	if (fc_rows >= 0)
		fc_pathnode->path.rows = fc_rows;

	return fc_pathnode;
}

/*
 * append_total_cost_compare
 *	  用于按总成本降序排序附加子路径的list_sort比较器
 *
 * 对于相等的总成本，我们回退到比较启动成本；如果它们也相等，则使用bms_compare对路径的relids进行平局处理。
 * （这是为了避免从list_sort得到不可预测的结果。）
 */
static int fc_append_total_cost_compare(const ListCell *fc_a, const ListCell *fc_b)
{
	Path	   *fc_path1 = (Path *) lfirst(fc_a);
	Path	   *fc_path2 = (Path *) lfirst(fc_b);
	int			fc_cmp;

	fc_cmp = compare_path_costs(fc_path1, fc_path2, TOTAL_COST);
	if (fc_cmp != 0)
		return -fc_cmp;
	return bms_compare(fc_path1->parent->relids, fc_path2->parent->relids);
}

/*
 * append_startup_cost_compare
 *	  用于按启动成本降序排序附加子路径的list_sort比较器
 *
 * 对于相等的启动成本，我们回退到比较总成本；如果它们也相等，则使用bms_compare对路径的relids进行平局处理。
 * （这是为了避免从list_sort得到不可预测的结果。）
 */
static int fc_append_startup_cost_compare(const ListCell *fc_a, const ListCell *fc_b)
{
	Path	   *fc_path1 = (Path *) lfirst(fc_a);
	Path	   *fc_path2 = (Path *) lfirst(fc_b);
	int			fc_cmp;

	fc_cmp = compare_path_costs(fc_path1, fc_path2, STARTUP_COST);
	if (fc_cmp != 0)
		return -fc_cmp;
	return bms_compare(fc_path1->parent->relids, fc_path2->parent->relids);
}

/*
 * create_merge_append_path
 *	  创建一个对应于MergeAppend计划的路径，并返回路径节点。
 */
MergeAppendPath *
create_merge_append_path(PlannerInfo *fc_root,
						 RelOptInfo *fc_rel,
						 List *fc_subpaths,
						 List *fc_pathkeys,
						 Relids fc_required_outer)
{
	MergeAppendPath *fc_pathnode = makeNode(MergeAppendPath);
	Cost		fc_input_startup_cost;
	Cost		fc_input_total_cost;
	ListCell   *fc_l;

	fc_pathnode->path.pathtype = T_MergeAppend;
	fc_pathnode->path.parent = fc_rel;
	fc_pathnode->path.pathtarget = fc_rel->reltarget;
	fc_pathnode->path.param_info = get_appendrel_parampathinfo(fc_rel,
															fc_required_outer);
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel;
	fc_pathnode->path.parallel_workers = 0;
	fc_pathnode->path.pathkeys = fc_pathkeys;
	fc_pathnode->subpaths = fc_subpaths;

	/*
	 * 如果已知且路径是唯一基础关系，请应用查询范围内的LIMIT。
	 * （在这个低级别处理这个有点笨拙。）
	 */
	if (bms_equal(fc_rel->relids, fc_root->all_baserels))
		fc_pathnode->limit_tuples = fc_root->limit_tuples;
	else
		fc_pathnode->limit_tuples = -1.0;

	/*
	 * 对输入路径的大小和成本进行汇总。
	 */
	fc_pathnode->path.rows = 0;
	fc_input_startup_cost = 0;
	fc_input_total_cost = 0;
	foreach(fc_l, fc_subpaths)
	{
		Path	   *fc_subpath = (Path *) lfirst(fc_l);

		fc_pathnode->path.rows += fc_subpath->rows;
		fc_pathnode->path.parallel_safe = fc_pathnode->path.parallel_safe &&
			fc_subpath->parallel_safe;

		if (pathkeys_contained_in(fc_pathkeys, fc_subpath->pathkeys))
		{
			/* 子路径已适当地排序，我们不需要对其进行排序 */
			fc_input_startup_cost += fc_subpath->startup_cost;
			fc_input_total_cost += fc_subpath->total_cost;
		}
		else
		{
			/* 我们需要插入一个Sort节点，因此包括其成本 */
			Path		sort_path;	/* cost_sort 结果的占位符 */

			cost_sort(&sort_path,
					  fc_root,
					  fc_pathkeys,
					  fc_subpath->total_cost,
					  fc_subpath->parent->tuples,
					  fc_subpath->pathtarget->width,
					  0.0,
					  work_mem,
					  fc_pathnode->limit_tuples);
			fc_input_startup_cost += sort_path.startup_cost;
			fc_input_total_cost += sort_path.total_cost;
		}

		/* 所有子路径必须具有相同的参数化 */
		Assert(bms_equal(PATH_REQ_OUTER(fc_subpath), fc_required_outer));
	}

	/*
	 * 现在我们可以计算MergeAppend的总成本。如果只有一个子路径，MergeAppend就是一个无操作，并将在稍后被丢弃
	 * （在setrefs.c中）；否则我们做正常的成本计算。
	 */
	if (list_length(fc_subpaths) == 1)
	{
		fc_pathnode->path.startup_cost = fc_input_startup_cost;
		fc_pathnode->path.total_cost = fc_input_total_cost;
	}
	else
		cost_merge_append(&fc_pathnode->path, fc_root,
						  fc_pathkeys, list_length(fc_subpaths),
						  fc_input_startup_cost, fc_input_total_cost,
						  fc_pathnode->path.rows);

	return fc_pathnode;
}

/*
 * create_group_result_path
 *	  创建一个表示Result-and-nothing-else计划的路径。
 *
 * 这仅用于退化分组案例，在此我们知道我们
 * 需要生成一行结果，可能由HAVING资格过滤。
 */
GroupResultPath *
create_group_result_path(PlannerInfo *fc_root, RelOptInfo *fc_rel,
						 PathTarget *fc_target, List *fc_havingqual)
{
	GroupResultPath *fc_pathnode = makeNode(GroupResultPath);

	fc_pathnode->path.pathtype = T_Result;
	fc_pathnode->path.parent = fc_rel;
	fc_pathnode->path.pathtarget = fc_target;
	fc_pathnode->path.param_info = NULL;	/* 没有其他关系... */
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel;
	fc_pathnode->path.parallel_workers = 0;
	fc_pathnode->path.pathkeys = NIL;
	fc_pathnode->quals = fc_havingqual;

	/*
	 * 我们无法完全使用cost_resultscan()，因为我们想要考虑的qual不是关系的baserestrict quals。
	 * 不如直接在这里处理。
	 */
	fc_pathnode->path.rows = 1;
	fc_pathnode->path.startup_cost = fc_target->cost.startup;
	fc_pathnode->path.total_cost = fc_target->cost.startup +
		cpu_tuple_cost + fc_target->cost.per_tuple;

	/*
	 * 如果有qual，则添加qual的成本——但是我们忽略其选择性，因为我们的
	 * 行数估计应该是1，无论qual是什么。
	 */
	if (fc_havingqual)
	{
		QualCost	fc_qual_cost;

		cost_qual_eval(&fc_qual_cost, fc_havingqual, fc_root);
		/* havingqual在启动时评估一次 */
		fc_pathnode->path.startup_cost += fc_qual_cost.startup + fc_qual_cost.per_tuple;
		fc_pathnode->path.total_cost += fc_qual_cost.startup + fc_qual_cost.per_tuple;
	}

	return fc_pathnode;
}

/*
 * create_material_path
 *	  创建一个对应于Material计划的路径，并返回路径节点。
 */
MaterialPath *
create_material_path(RelOptInfo *fc_rel, Path *fc_subpath)
{
	MaterialPath *fc_pathnode = makeNode(MaterialPath);

	Assert(fc_subpath->parent == fc_rel);

	fc_pathnode->path.pathtype = T_Material;
	fc_pathnode->path.parent = fc_rel;
	fc_pathnode->path.pathtarget = fc_rel->reltarget;
	fc_pathnode->path.param_info = fc_subpath->param_info;
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel &&
		fc_subpath->parallel_safe;
	fc_pathnode->path.parallel_workers = fc_subpath->parallel_workers;
	fc_pathnode->path.pathkeys = fc_subpath->pathkeys;

	fc_pathnode->subpath = fc_subpath;

	cost_material(&fc_pathnode->path,
				  fc_subpath->startup_cost,
				  fc_subpath->total_cost,
				  fc_subpath->rows,
				  fc_subpath->pathtarget->width);

	return fc_pathnode;
}

/*
 * create_memoize_path
 *	  创建一个对应于Memoize计划的路径，并返回路径节点。
 */
MemoizePath *
create_memoize_path(PlannerInfo *fc_root, RelOptInfo *fc_rel, Path *fc_subpath,
					List *fc_param_exprs, List *fc_hash_operators,
					bool fc_singlerow, bool fc_binary_mode, double fc_calls)
{
	MemoizePath *fc_pathnode = makeNode(MemoizePath);

	Assert(fc_subpath->parent == fc_rel);

	fc_pathnode->path.pathtype = T_Memoize;
	fc_pathnode->path.parent = fc_rel;
	fc_pathnode->path.pathtarget = fc_rel->reltarget;
	fc_pathnode->path.param_info = fc_subpath->param_info;
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel &&
		fc_subpath->parallel_safe;
	fc_pathnode->path.parallel_workers = fc_subpath->parallel_workers;
	fc_pathnode->path.pathkeys = fc_subpath->pathkeys;

	fc_pathnode->subpath = fc_subpath;
	fc_pathnode->hash_operators = fc_hash_operators;
	fc_pathnode->param_exprs = fc_param_exprs;
	fc_pathnode->singlerow = fc_singlerow;
	fc_pathnode->binary_mode = fc_binary_mode;
	fc_pathnode->calls = clamp_row_est(fc_calls);

	
/*
	 * 目前我们将 est_entries 设置为 0。cost_memoize_rescan() 完成了所有
	 * 确定可能有多少缓存条目的繁重工作，因此将这个字段的填写留给该函数似乎是最佳选择。
	 * 如果保持为 0，执行器将对一个好的值做出猜测。
	 */
	fc_pathnode->est_entries = 0;

	/*
	 * 为缓存第一个条目增加一个小额附加费用。所有用于重新扫描的
	 * 较复杂计算都在 cost_memoize_rescan() 中进行。
	 */
	fc_pathnode->path.startup_cost = fc_subpath->startup_cost + cpu_tuple_cost;
	fc_pathnode->path.total_cost = fc_subpath->total_cost + cpu_tuple_cost;
	fc_pathnode->path.rows = fc_subpath->rows;

	return fc_pathnode;
}

/*
 * create_unique_path
 *	  创建一个路径，以表示从输入数据中消除不同的行。
 *	  不同性根据 sjinfo 表示的半连接的需求进行定义。
 *	  如果无法确定如何使数据唯一，则返回 NULL。
 *
 * 如果调用，可能会在同一个 rel 上重复调用；
 * 输入子路径应始终相同（该 rel 的 cheapest_total 路径）。
 * 因此我们缓存结果。
 */
UniquePath *
create_unique_path(PlannerInfo *fc_root, RelOptInfo *fc_rel, Path *fc_subpath,
				   SpecialJoinInfo *fc_sjinfo)
{
	UniquePath *fc_pathnode;
	Path		sort_path;		/* cost_sort 结果的占位符 */
	Path		fc_agg_path;		/* 成本聚合结果的虚拟对象 */
	MemoryContext fc_oldcontext;
	int			fc_numCols;

	/* 如果子路径不是 cheapest_total，则调用者犯了一个错误... */
	Assert(fc_subpath == fc_rel->cheapest_total_path);
	Assert(fc_subpath->parent == fc_rel);
	/* ...或者如果 SpecialJoinInfo 是错误的 */
	Assert(fc_sjinfo->jointype == JOIN_SEMI);
	Assert(bms_equal(fc_rel->relids, fc_sjinfo->syn_righthand));

	/* 如果结果已经缓存，返回它 */
	if (fc_rel->cheapest_unique_path)
		return (UniquePath *) fc_rel->cheapest_unique_path;

	/* 如果无法唯一化，返回 NULL */
	if (!(fc_sjinfo->semi_can_btree || fc_sjinfo->semi_can_hash))
		return NULL;

	/*
	 * 在 GEQO 连接规划中调用时，我们处于一个短暂的内存
	 * 上下文中。我们必须确保为 baserel 创建的路径和任何附属数据
	 * 结构在 GEQO 循环中生存，否则 baserel 将在未来的 GEQO 循环中被破坏。
	 * 另一方面，当我们在 GEQO 中为 joinrel 创建这些时，我们不希望它们
	 * 使主要的规划上下文混乱。最终，我们的最佳解决方案是
	 * 在给定的 RelOptInfo 所在的上下文中显式分配内存。
	 */
	fc_oldcontext = MemoryContextSwitchTo(GetMemoryChunkContext(fc_rel));

	fc_pathnode = makeNode(UniquePath);

	fc_pathnode->path.pathtype = T_Unique;
	fc_pathnode->path.parent = fc_rel;
	fc_pathnode->path.pathtarget = fc_rel->reltarget;
	fc_pathnode->path.param_info = fc_subpath->param_info;
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel &&
		fc_subpath->parallel_safe;
	fc_pathnode->path.parallel_workers = fc_subpath->parallel_workers;

	/*
	 * 假设输出是未排序的，因为我们不一定有表示它的 pathkeys
	 * 。 (下面可能会覆盖此内容。) */
	fc_pathnode->path.pathkeys = NIL;

	fc_pathnode->subpath = fc_subpath;

	/*
	 * 在 GEQO 下，sjinfo 可能是短暂的，因此我们最好复制我们提取的
	 * 数据结构。 */
	fc_pathnode->in_operators = copyObject(fc_sjinfo->semi_operators);
	fc_pathnode->uniq_exprs = copyObject(fc_sjinfo->semi_rhs_exprs);

	/*
	 * 如果输入是关系并且具有证明 semi_rhs_exprs 唯一性的唯一索引，
	 * 那么我们不需要做任何事情。请注意，relation_has_unique_index_for 
	 * 自动考虑该关系的限制子句。 */
	if (fc_rel->rtekind == RTE_RELATION && fc_sjinfo->semi_can_btree &&
		relation_has_unique_index_for(fc_root, fc_rel, NIL,
									  fc_sjinfo->semi_rhs_exprs,
									  fc_sjinfo->semi_operators))
	{
		fc_pathnode->umethod = UNIQUE_PATH_NOOP;
		fc_pathnode->path.rows = fc_rel->rows;
		fc_pathnode->path.startup_cost = fc_subpath->startup_cost;
		fc_pathnode->path.total_cost = fc_subpath->total_cost;
		fc_pathnode->path.pathkeys = fc_subpath->pathkeys;

		fc_rel->cheapest_unique_path = (Path *) fc_pathnode;

		MemoryContextSwitchTo(fc_oldcontext);

		return fc_pathnode;
	}

	/*
	 * 如果输入是必须唯一的子查询的输出，那么我们不需要做任何事情。
	 * 唯一性的测试必须考虑到我们提取的确切列；例如
	 * “SELECT DISTINCT x,y”并不保证 x 独立。因此，除非 semi_rhs_exprs 
	 * 只包含引用子查询输出的简单 Vars，否则我们无法检查此优化。
	 * (可能我们也可以对子查询输出中的表达式做一些事情，但现在保持
	 * 简单。) */
	if (fc_rel->rtekind == RTE_SUBQUERY)
	{
		RangeTblEntry *fc_rte = planner_rt_fetch(fc_rel->relid, fc_root);

		if (query_supports_distinctness(fc_rte->subquery))
		{
			List	   *fc_sub_tlist_colnos;

			fc_sub_tlist_colnos = fc_translate_sub_tlist(fc_sjinfo->semi_rhs_exprs,
												   fc_rel->relid);

			if (fc_sub_tlist_colnos &&
				query_is_distinct_for(fc_rte->subquery,
									  fc_sub_tlist_colnos,
									  fc_sjinfo->semi_operators))
			{
				fc_pathnode->umethod = UNIQUE_PATH_NOOP;
				fc_pathnode->path.rows = fc_rel->rows;
				fc_pathnode->path.startup_cost = fc_subpath->startup_cost;
				fc_pathnode->path.total_cost = fc_subpath->total_cost;
				fc_pathnode->path.pathkeys = fc_subpath->pathkeys;

				fc_rel->cheapest_unique_path = (Path *) fc_pathnode;

				MemoryContextSwitchTo(fc_oldcontext);

				return fc_pathnode;
			}
		}
	}

	/* 估计输出行数 */
	fc_pathnode->path.rows = estimate_num_groups(fc_root,
											  fc_sjinfo->semi_rhs_exprs,
											  fc_rel->rows,
											  NULL,
											  NULL);
	fc_numCols = list_length(fc_sjinfo->semi_rhs_exprs);

	if (fc_sjinfo->semi_can_btree)
	{
		/*
		 * 估计排序+唯一实现的成本
		 */
		cost_sort(&sort_path, fc_root, NIL,
				  fc_subpath->total_cost,
				  fc_rel->rows,
				  fc_subpath->pathtarget->width,
				  0.0,
				  work_mem,
				  -1.0);

		/*
		 * 每个输入元组的比较收取一个 cpu_operator_cost。我们
		 * 假设所有列都在大多数元组中进行比较。(XXX
		 * 这可能是高估。)这应该与 create_upper_unique_path 一致。 */
		sort_path.total_cost += cpu_operator_cost * fc_rel->rows * fc_numCols;
	}

	if (fc_sjinfo->semi_can_hash)
	{
		/*
		 * 估计每个哈希表条目的开销为 64 字节（与 planner.c 中相同）。
		 */
		int			fc_hashentrysize = fc_subpath->pathtarget->width + 64;

		if (fc_hashentrysize * fc_pathnode->path.rows > get_hash_memory_limit())
		{
			/*
			 * 我们不应该尝试哈希。黑客利用 SpecialJoinInfo 
			 * 记住这一点，以防我们再次经过这里。 */
			fc_sjinfo->semi_can_hash = false;
		}
		else
			cost_agg(&fc_agg_path, fc_root,
					 AGG_HASHED, NULL,
					 fc_numCols, fc_pathnode->path.rows,
					 NIL,
					 fc_subpath->startup_cost,
					 fc_subpath->total_cost,
					 fc_rel->rows,
					 fc_subpath->pathtarget->width);
	}

	if (fc_sjinfo->semi_can_btree && fc_sjinfo->semi_can_hash)
	{
		if (fc_agg_path.total_cost < sort_path.total_cost)
			fc_pathnode->umethod = UNIQUE_PATH_HASH;
		else
			fc_pathnode->umethod = UNIQUE_PATH_SORT;
	}
	else if (fc_sjinfo->semi_can_btree)
		fc_pathnode->umethod = UNIQUE_PATH_SORT;
	else if (fc_sjinfo->semi_can_hash)
		fc_pathnode->umethod = UNIQUE_PATH_HASH;
	else
	{
		/* 我们只能在上面放弃哈希的情况下到达这里 */
		MemoryContextSwitchTo(fc_oldcontext);
		return NULL;
	}

	if (fc_pathnode->umethod == UNIQUE_PATH_HASH)
	{
		fc_pathnode->path.startup_cost = fc_agg_path.startup_cost;
		fc_pathnode->path.total_cost = fc_agg_path.total_cost;
	}
	else
	{
		fc_pathnode->path.startup_cost = sort_path.startup_cost;
		fc_pathnode->path.total_cost = sort_path.total_cost;
	}

	fc_rel->cheapest_unique_path = (Path *) fc_pathnode;

	MemoryContextSwitchTo(fc_oldcontext);

	return fc_pathnode;
}

/*
 * create_gather_merge_path
 *
 *	  创建与汇聚合并扫描相对应的路径，返回
 *	  pathnode。
 */
GatherMergePath *
create_gather_merge_path(PlannerInfo *fc_root, RelOptInfo *fc_rel, Path *fc_subpath,
						 PathTarget *fc_target, List *fc_pathkeys,
						 Relids fc_required_outer, double *fc_rows)
{
	GatherMergePath *fc_pathnode = makeNode(GatherMergePath);
	Cost		fc_input_startup_cost = 0;
	Cost		fc_input_total_cost = 0;

	Assert(fc_subpath->parallel_safe);
	Assert(fc_pathkeys);

	fc_pathnode->path.pathtype = T_GatherMerge;
	fc_pathnode->path.parent = fc_rel;
	fc_pathnode->path.param_info = get_baserel_parampathinfo(fc_root, fc_rel,
														  fc_required_outer);
	fc_pathnode->path.parallel_aware = false;

	fc_pathnode->subpath = fc_subpath;
	fc_pathnode->num_workers = fc_subpath->parallel_workers;
	fc_pathnode->path.pathkeys = fc_pathkeys;
	fc_pathnode->path.pathtarget = fc_target ? fc_target : fc_rel->reltarget;
	fc_pathnode->path.rows += fc_subpath->rows;

	if (pathkeys_contained_in(fc_pathkeys, fc_subpath->pathkeys))
	{
		/* 子路径已适当地排序，我们不需要对其进行排序 */
		fc_input_startup_cost += fc_subpath->startup_cost;
		fc_input_total_cost += fc_subpath->total_cost;
	}
	else
	{
		/* 我们需要插入一个Sort节点，因此包括其成本 */
		Path		sort_path;	/* cost_sort 结果的占位符 */

		cost_sort(&sort_path,
				  fc_root,
				  fc_pathkeys,
				  fc_subpath->total_cost,
				  fc_subpath->rows,
				  fc_subpath->pathtarget->width,
				  0.0,
				  work_mem,
				  -1);
		fc_input_startup_cost += sort_path.startup_cost;
		fc_input_total_cost += sort_path.total_cost;
	}

	cost_gather_merge(fc_pathnode, fc_root, fc_rel, fc_pathnode->path.param_info,
					  fc_input_startup_cost, fc_input_total_cost, fc_rows);

	return fc_pathnode;
}

/*
 * translate_sub_tlist - 获取由 tlist 表示的子查询列号
 *
 * 给定的目标列表通常只包含引用给定 relid 的 Vars。
 * 提取它们的 varattnos（即子查询的列号）并返回
 * 作为整数列表。
 *
 * 如果任何 tlist 项不是简单的 Var，我们无法确定
 * 子查询的唯一性条件（如果有）是否与我们的匹配，因此放弃并
 * 返回 NIL。
 */
static List * fc_translate_sub_tlist(List *fc_tlist, int fc_relid)
{
	List	   *fc_result = NIL;
	ListCell   *fc_l;

	foreach(fc_l, fc_tlist)
	{
		Var		   *fc_var = (Var *) lfirst(fc_l);

		if (!fc_var || !IsA(fc_var, Var) ||
			fc_var->varno != fc_relid)
			return NIL;			/* punt */

		fc_result = lappend_int(fc_result, fc_var->varattno);
	}
	return fc_result;
}

/*
 * create_gather_path
 *	  创建与汇聚扫描相对应的路径，返回
 *	  pathnode。
 *
 * 'rows' 可选设置以覆盖来自其他来源的行估计。
 */
GatherPath *
create_gather_path(PlannerInfo *fc_root, RelOptInfo *fc_rel, Path *fc_subpath,
				   PathTarget *fc_target, Relids fc_required_outer, double *fc_rows)
{
	GatherPath *fc_pathnode = makeNode(GatherPath);

	Assert(fc_subpath->parallel_safe);

	fc_pathnode->path.pathtype = T_Gather;
	fc_pathnode->path.parent = fc_rel;
	fc_pathnode->path.pathtarget = fc_target;
	fc_pathnode->path.param_info = get_baserel_parampathinfo(fc_root, fc_rel,
														  fc_required_outer);
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = false;
	fc_pathnode->path.parallel_workers = 0;
	fc_pathnode->path.pathkeys = NIL;	/* 汇聚结果是无序的 */

	fc_pathnode->subpath = fc_subpath;
	fc_pathnode->num_workers = fc_subpath->parallel_workers;
	fc_pathnode->single_copy = false;

	if (fc_pathnode->num_workers == 0)
	{
		fc_pathnode->path.pathkeys = fc_subpath->pathkeys;
		fc_pathnode->num_workers = 1;
		fc_pathnode->single_copy = true;
	}

	cost_gather(fc_pathnode, fc_root, fc_rel, fc_pathnode->path.param_info, fc_rows);

	return fc_pathnode;
}

/*
 * create_subqueryscan_path
 *	  创建与子查询扫描相对应的路径，
 *	  返回 pathnode。
 */
SubqueryScanPath *
create_subqueryscan_path(PlannerInfo *fc_root, RelOptInfo *fc_rel, Path *fc_subpath,
						 List *fc_pathkeys, Relids fc_required_outer)
{
	SubqueryScanPath *fc_pathnode = makeNode(SubqueryScanPath);

	fc_pathnode->path.pathtype = T_SubqueryScan;
	fc_pathnode->path.parent = fc_rel;
	fc_pathnode->path.pathtarget = fc_rel->reltarget;
	fc_pathnode->path.param_info = get_baserel_parampathinfo(fc_root, fc_rel,
														  fc_required_outer);
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel &&
		fc_subpath->parallel_safe;
	fc_pathnode->path.parallel_workers = fc_subpath->parallel_workers;
	fc_pathnode->path.pathkeys = fc_pathkeys;
	fc_pathnode->subpath = fc_subpath;

	cost_subqueryscan(fc_pathnode, fc_root, fc_rel, fc_pathnode->path.param_info);

	return fc_pathnode;
}

/*
 * create_functionscan_path
 *	  创建与函数的顺序扫描相对应的路径，
 *	  返回 pathnode。
 */
Path * create_functionscan_path(PlannerInfo *fc_root, RelOptInfo *fc_rel,
						 List *fc_pathkeys, Relids fc_required_outer)
{
	Path	   *fc_pathnode = makeNode(Path);

	fc_pathnode->pathtype = T_FunctionScan;
	fc_pathnode->parent = fc_rel;
	fc_pathnode->pathtarget = fc_rel->reltarget;
	fc_pathnode->param_info = get_baserel_parampathinfo(fc_root, fc_rel,
													 fc_required_outer);
	fc_pathnode->parallel_aware = false;
	fc_pathnode->parallel_safe = fc_rel->consider_parallel;
	fc_pathnode->parallel_workers = 0;
	fc_pathnode->pathkeys = fc_pathkeys;

	cost_functionscan(fc_pathnode, fc_root, fc_rel, fc_pathnode->param_info);

	return fc_pathnode;
}

/*
 * create_tablefuncscan_path
 *	  创建与表函数的顺序扫描相对应的路径，
 *	  返回 pathnode。
 */
Path * create_tablefuncscan_path(PlannerInfo *fc_root, RelOptInfo *fc_rel,
						  Relids fc_required_outer)
{
	Path	   *fc_pathnode = makeNode(Path);

	fc_pathnode->pathtype = T_TableFuncScan;
	fc_pathnode->parent = fc_rel;
	fc_pathnode->pathtarget = fc_rel->reltarget;
	fc_pathnode->param_info = get_baserel_parampathinfo(fc_root, fc_rel,
													 fc_required_outer);
	fc_pathnode->parallel_aware = false;
	fc_pathnode->parallel_safe = fc_rel->consider_parallel;
	fc_pathnode->parallel_workers = 0;
	fc_pathnode->pathkeys = NIL;	/* 结果始终是无序的 */

	cost_tablefuncscan(fc_pathnode, fc_root, fc_rel, fc_pathnode->param_info);

	return fc_pathnode;
}

/*
 * create_valuesscan_path
 *	  创建与 VALUES 列表的扫描相对应的路径，
 *	  返回 pathnode。
 */
Path * create_valuesscan_path(PlannerInfo *fc_root, RelOptInfo *fc_rel,
					   Relids fc_required_outer)
{
	Path	   *fc_pathnode = makeNode(Path);

	fc_pathnode->pathtype = T_ValuesScan;
	fc_pathnode->parent = fc_rel;
	fc_pathnode->pathtarget = fc_rel->reltarget;
	fc_pathnode->param_info = get_baserel_parampathinfo(fc_root, fc_rel,
													 fc_required_outer);
	fc_pathnode->parallel_aware = false;
	fc_pathnode->parallel_safe = fc_rel->consider_parallel;
	fc_pathnode->parallel_workers = 0;
	fc_pathnode->pathkeys = NIL;	/* 结果始终是无序的 */

	cost_valuesscan(fc_pathnode, fc_root, fc_rel, fc_pathnode->param_info);

	return fc_pathnode;
}

/*
 * create_ctescan_path
 *	  创建与非自引用 CTE 的扫描相对应的路径，
 *	  返回 pathnode。
 */
Path * create_ctescan_path(PlannerInfo *fc_root, RelOptInfo *fc_rel, Relids fc_required_outer)
{
	Path	   *fc_pathnode = makeNode(Path);

	fc_pathnode->pathtype = T_CteScan;
	fc_pathnode->parent = fc_rel;
	fc_pathnode->pathtarget = fc_rel->reltarget;
	fc_pathnode->param_info = get_baserel_parampathinfo(fc_root, fc_rel,
													 fc_required_outer);
	fc_pathnode->parallel_aware = false;
	fc_pathnode->parallel_safe = fc_rel->consider_parallel;
	fc_pathnode->parallel_workers = 0;
	fc_pathnode->pathkeys = NIL;	/* XXX 目前，结果始终是无序的 */

	cost_ctescan(fc_pathnode, fc_root, fc_rel, fc_pathnode->param_info);

	return fc_pathnode;
}

/*
 * create_namedtuplestorescan_path
 *	  创建与命名元组存储的扫描相对应的路径，返回
 *	  pathnode。
 */
Path * create_namedtuplestorescan_path(PlannerInfo *fc_root, RelOptInfo *fc_rel,
								Relids fc_required_outer)
{
	Path	   *fc_pathnode = makeNode(Path);

	fc_pathnode->pathtype = T_NamedTuplestoreScan;
	fc_pathnode->parent = fc_rel;
	fc_pathnode->pathtarget = fc_rel->reltarget;
	fc_pathnode->param_info = get_baserel_parampathinfo(fc_root, fc_rel,
													 fc_required_outer);
	fc_pathnode->parallel_aware = false;
	fc_pathnode->parallel_safe = fc_rel->consider_parallel;
	fc_pathnode->parallel_workers = 0;
	fc_pathnode->pathkeys = NIL;	/* 结果始终是无序的 */

	cost_namedtuplestorescan(fc_pathnode, fc_root, fc_rel, fc_pathnode->param_info);

	return fc_pathnode;
}

/*
 * create_resultscan_path
 *	  创建与 RTE_RESULT 关系的扫描相对应的路径，
 *	  返回 pathnode。
 */
Path * create_resultscan_path(PlannerInfo *fc_root, RelOptInfo *fc_rel,
					   Relids fc_required_outer)
{
	Path	   *fc_pathnode = makeNode(Path);

	fc_pathnode->pathtype = T_Result;
	fc_pathnode->parent = fc_rel;
	fc_pathnode->pathtarget = fc_rel->reltarget;
	fc_pathnode->param_info = get_baserel_parampathinfo(fc_root, fc_rel,
													 fc_required_outer);
	fc_pathnode->parallel_aware = false;
	fc_pathnode->parallel_safe = fc_rel->consider_parallel;
	fc_pathnode->parallel_workers = 0;
	fc_pathnode->pathkeys = NIL;	/* 结果始终是无序的 */

	cost_resultscan(fc_pathnode, fc_root, fc_rel, fc_pathnode->param_info);

	return fc_pathnode;
}

/*
 * create_worktablescan_path
 *	  创建一个对应于自引用 CTE 扫描的路径，
 *	  返回路径节点。
 */
Path * create_worktablescan_path(PlannerInfo *fc_root, RelOptInfo *fc_rel,
						  Relids fc_required_outer)
{
	Path	   *fc_pathnode = makeNode(Path);

	fc_pathnode->pathtype = T_WorkTableScan;
	fc_pathnode->parent = fc_rel;
	fc_pathnode->pathtarget = fc_rel->reltarget;
	fc_pathnode->param_info = get_baserel_parampathinfo(fc_root, fc_rel,
													 fc_required_outer);
	fc_pathnode->parallel_aware = false;
	fc_pathnode->parallel_safe = fc_rel->consider_parallel;
	fc_pathnode->parallel_workers = 0;
	fc_pathnode->pathkeys = NIL;	/* 结果始终是无序的 */

	/* 成本与常规 CTE 扫描相同 */
	cost_ctescan(fc_pathnode, fc_root, fc_rel, fc_pathnode->param_info);

	return fc_pathnode;
}

/*
 * create_foreignscan_path
 *	  创建一个对应于外部基本表扫描的路径，
 *	  返回路径节点。
 *
 * 该函数从未被核心 Postgres 调用；相反，预计由外部数据包装器的 GetForeignPaths 函数调用。
 * 我们让 FDW 提供路径的所有字段，因为我们在核心中没有任何方法来计算它们。
 * 然而，pathtarget（rel->reltarget）通常有一个合理的默认值，因此我们允许 "target" 为 NULL 来选择它。
 */
ForeignPath * create_foreignscan_path(PlannerInfo *fc_root, RelOptInfo *fc_rel,
						PathTarget *fc_target,
						double fc_rows, Cost fc_startup_cost, Cost fc_total_cost,
						List *fc_pathkeys,
						Relids fc_required_outer,
						Path *fc_fdw_outerpath,
						List *fc_fdw_private)
{
	ForeignPath *fc_pathnode = makeNode(ForeignPath);

	/* 历史上，某些 FDW 对何时使用这个感到困惑 */
	Assert(IS_SIMPLE_REL(fc_rel));

	fc_pathnode->path.pathtype = T_ForeignScan;
	fc_pathnode->path.parent = fc_rel;
	fc_pathnode->path.pathtarget = fc_target ? fc_target : fc_rel->reltarget;
	fc_pathnode->path.param_info = get_baserel_parampathinfo(fc_root, fc_rel,
														  fc_required_outer);
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel;
	fc_pathnode->path.parallel_workers = 0;
	fc_pathnode->path.rows = fc_rows;
	fc_pathnode->path.startup_cost = fc_startup_cost;
	fc_pathnode->path.total_cost = fc_total_cost;
	fc_pathnode->path.pathkeys = fc_pathkeys;

	fc_pathnode->fdw_outerpath = fc_fdw_outerpath;
	fc_pathnode->fdw_private = fc_fdw_private;

	return fc_pathnode;
}

/*
 * create_foreign_join_path
 *	  创建一个对应于外部连接扫描的路径，
 *	  返回路径节点。
 *
 * 该函数从未被核心 Postgres 调用；相反，预计由外部数据包装器的 GetForeignJoinPaths 函数调用。
 * 我们让 FDW 提供路径的所有字段，因为我们在核心中没有任何方法来计算它们。
 * 然而，pathtarget（rel->reltarget）通常有一个合理的默认值，因此我们允许 "target" 为 NULL 来选择它。
 */
ForeignPath * create_foreign_join_path(PlannerInfo *fc_root, RelOptInfo *fc_rel,
						 PathTarget *fc_target,
						 double fc_rows, Cost fc_startup_cost, Cost fc_total_cost,
						 List *fc_pathkeys,
						 Relids fc_required_outer,
						 Path *fc_fdw_outerpath,
						 List *fc_fdw_private)
{
	ForeignPath *fc_pathnode = makeNode(ForeignPath);

	/*
	 * 我们应该使用 get_joinrel_parampathinfo 来处理参数化路径，
	 * 但是该函数的 API 不支持此功能，并且现有的扩展也还未尝试构建这样的路径。
	 * 目前如果有人尝试，直接抛出错误；最终我们应该重新审视这个问题。
	 */
	if (!bms_is_empty(fc_required_outer) || !bms_is_empty(fc_rel->lateral_relids))
		elog(ERROR, "parameterized foreign joins are not supported yet");

	fc_pathnode->path.pathtype = T_ForeignScan;
	fc_pathnode->path.parent = fc_rel;
	fc_pathnode->path.pathtarget = fc_target ? fc_target : fc_rel->reltarget;
	fc_pathnode->path.param_info = NULL;	/* XXX 见上文 */
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel;
	fc_pathnode->path.parallel_workers = 0;
	fc_pathnode->path.rows = fc_rows;
	fc_pathnode->path.startup_cost = fc_startup_cost;
	fc_pathnode->path.total_cost = fc_total_cost;
	fc_pathnode->path.pathkeys = fc_pathkeys;

	fc_pathnode->fdw_outerpath = fc_fdw_outerpath;
	fc_pathnode->fdw_private = fc_fdw_private;

	return fc_pathnode;
}

/*
 * create_foreign_upper_path
 *	  创建一个对应于由 FDW 直接计算的上层关系的路径，
 *	  返回路径节点。
 *
 * 该函数从未被核心 Postgres 调用；相反，预计由外部数据包装器的 GetForeignUpperPaths 函数调用。
 * 我们让 FDW 提供路径的所有字段，因为我们在核心中没有任何方法来计算它们。
 * 然而，pathtarget（rel->reltarget）通常有一个合理的默认值，因此我们允许 "target" 为 NULL 来选择它。
 */
ForeignPath * create_foreign_upper_path(PlannerInfo *fc_root, RelOptInfo *fc_rel,
						  PathTarget *fc_target,
						  double fc_rows, Cost fc_startup_cost, Cost fc_total_cost,
						  List *fc_pathkeys,
						  Path *fc_fdw_outerpath,
						  List *fc_fdw_private)
{
	ForeignPath *fc_pathnode = makeNode(ForeignPath);

	/*
	 * 上层关系永远不应该有任何横向引用，因为连接是完整的。
	 */
	Assert(bms_is_empty(fc_rel->lateral_relids));

	fc_pathnode->path.pathtype = T_ForeignScan;
	fc_pathnode->path.parent = fc_rel;
	fc_pathnode->path.pathtarget = fc_target ? fc_target : fc_rel->reltarget;
	fc_pathnode->path.param_info = NULL;
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel;
	fc_pathnode->path.parallel_workers = 0;
	fc_pathnode->path.rows = fc_rows;
	fc_pathnode->path.startup_cost = fc_startup_cost;
	fc_pathnode->path.total_cost = fc_total_cost;
	fc_pathnode->path.pathkeys = fc_pathkeys;

	fc_pathnode->fdw_outerpath = fc_fdw_outerpath;
	fc_pathnode->fdw_private = fc_fdw_private;

	return fc_pathnode;
}

/*
 * calc_nestloop_required_outer
 *	  计算嵌套循环连接路径的 required_outer 集合
 *
 * 注意：结果不能与任何输入共享存储
 */
Relids calc_nestloop_required_outer(Relids fc_outerrelids,
							 Relids fc_outer_paramrels,
							 Relids fc_innerrelids,
							 Relids fc_inner_paramrels)
{
	Relids		fc_required_outer;

	/* inner_path 可以要求 outer_path 中的关系，但反之不可以 */
	Assert(!bms_overlap(fc_outer_paramrels, fc_innerrelids));
	/* 如果 inner_path 不是参数化的，情况很简单 */
	if (!fc_inner_paramrels)
		return bms_copy(fc_outer_paramrels);
	/* 否则，形成并集 ... */
	fc_required_outer = bms_union(fc_outer_paramrels, fc_inner_paramrels);
	/* ... 并移除任何提及现在已满足的外部关系 */
	fc_required_outer = bms_del_members(fc_required_outer,
									 fc_outerrelids);
	/* 维持不变的条件：如果为空，则 required_outer 恰好为 NULL */
	if (bms_is_empty(fc_required_outer))
	{
		bms_free(fc_required_outer);
		fc_required_outer = NULL;
	}
	return fc_required_outer;
}

/*
 * calc_non_nestloop_required_outer
 *	  计算合并或哈希连接路径的 required_outer 集合
 *
 * 注意：结果不能与任何输入共享存储
 */
Relids calc_non_nestloop_required_outer(Path *fc_outer_path, Path *fc_inner_path)
{
	Relids		fc_outer_paramrels = PATH_REQ_OUTER(fc_outer_path);
	Relids		fc_inner_paramrels = PATH_REQ_OUTER(fc_inner_path);
	Relids		fc_required_outer;

	/* 两个路径之间不能相互要求关系 */
	Assert(!bms_overlap(fc_outer_paramrels, fc_inner_path->parent->relids));
	Assert(!bms_overlap(fc_inner_paramrels, fc_outer_path->parent->relids));
	/* 形成并集 ... */
	fc_required_outer = bms_union(fc_outer_paramrels, fc_inner_paramrels);
	/* 我们不需要明确测试空；bms_union 可以正确处理 */
	return fc_required_outer;
}

/*
 * create_nestloop_path
 *	  创建一个对应于两个关系之间的嵌套循环连接的路径节点。
 *
 * 'joinrel' 是连接关系。
 * 'jointype' 是所需的连接类型。
 * 'workspace' 是来自 initial_cost_nestloop 的结果。
 * 'extra' 包含有关连接的各种信息。
 * 'outer_path' 是外部路径。
 * 'inner_path' 是内部路径。
 * 'restrict_clauses' 是在连接时应用的 RestrictInfo 节点。
 * 'pathkeys' 是新连接路径的路径键。
 * 'required_outer' 是所需外部关系的集合。
 *
 * 返回生成的路径节点。
 */
NestPath *
create_nestloop_path(PlannerInfo *fc_root,
					 RelOptInfo *fc_joinrel,
					 JoinType fc_jointype,
					 JoinCostWorkspace *fc_workspace,
					 JoinPathExtraData *fc_extra,
					 Path *fc_outer_path,
					 Path *fc_inner_path,
					 List *fc_restrict_clauses,
					 List *fc_pathkeys,
					 Relids fc_required_outer)
{
	NestPath   *fc_pathnode = makeNode(NestPath);
	Relids		fc_inner_req_outer = PATH_REQ_OUTER(fc_inner_path);

	/*
	 * 如果内部路径被外部参数化，我们必须去掉任何由于要移动到内部路径而产生的 restrict_clauses。
	 * 我们必须现在就做这件事，而不是推迟到 createplan 时，因为 restrict_clauses 列可能会影响这个路径的大小和成本估算。
	 */
	if (bms_overlap(fc_inner_req_outer, fc_outer_path->parent->relids))
	{
		Relids		fc_inner_and_outer = bms_union(fc_inner_path->parent->relids,
												fc_inner_req_outer);
		List	   *fc_jclauses = NIL;
		ListCell   *fc_lc;

		foreach(fc_lc, fc_restrict_clauses)
		{
			RestrictInfo *fc_rinfo = (RestrictInfo *) lfirst(fc_lc);

			if (!join_clause_is_movable_into(fc_rinfo,
											 fc_inner_path->parent->relids,
											 fc_inner_and_outer))
				fc_jclauses = lappend(fc_jclauses, fc_rinfo);
		}
		fc_restrict_clauses = fc_jclauses;
	}

	fc_pathnode->jpath.path.pathtype = T_NestLoop;
	fc_pathnode->jpath.path.parent = fc_joinrel;
	fc_pathnode->jpath.path.pathtarget = fc_joinrel->reltarget;
	fc_pathnode->jpath.path.param_info =
		get_joinrel_parampathinfo(fc_root,
								  fc_joinrel,
								  fc_outer_path,
								  fc_inner_path,
								  fc_extra->sjinfo,
								  fc_required_outer,
								  &fc_restrict_clauses);
	fc_pathnode->jpath.path.parallel_aware = false;
	fc_pathnode->jpath.path.parallel_safe = fc_joinrel->consider_parallel &&
		fc_outer_path->parallel_safe && fc_inner_path->parallel_safe;
	/* 这是一个愚蠢的方式来估算 parallel_workers，但暂时如此... */
	fc_pathnode->jpath.path.parallel_workers = fc_outer_path->parallel_workers;
	fc_pathnode->jpath.path.pathkeys = fc_pathkeys;
	fc_pathnode->jpath.jointype = fc_jointype;
	fc_pathnode->jpath.inner_unique = fc_extra->inner_unique;
	fc_pathnode->jpath.outerjoinpath = fc_outer_path;
	fc_pathnode->jpath.innerjoinpath = fc_inner_path;
	fc_pathnode->jpath.joinrestrictinfo = fc_restrict_clauses;

	final_cost_nestloop(fc_root, fc_pathnode, fc_workspace, fc_extra);

	return fc_pathnode;
}

/*
 * create_mergejoin_path
 *	  创建一个对应于两个关系之间的归并连接的路径节点。
 *
 * 'joinrel' 是连接关系。
 * 'jointype' 是所需的连接类型。
 * 'workspace' 是来自 initial_cost_mergejoin 的结果。
 * 'extra' 包含有关连接的各种信息。
 * 'outer_path' 是外部路径。
 * 'inner_path' 是内部路径。
 * 'restrict_clauses' 是在连接时应用的 RestrictInfo 节点。
 * 'pathkeys' 是新连接路径的路径键。
 * 'required_outer' 是所需外部关系的集合。
 * 'mergeclauses' 是用作合并子句的 RestrictInfo 节点（这应该是 restrict_clauses 列的子集）。
 * 'outersortkeys' 是外部关系的排序变量键。
 * 'innersortkeys' 是内部关系的排序变量键。
 */
MergePath *
create_mergejoin_path(PlannerInfo *fc_root,
					  RelOptInfo *fc_joinrel,
					  JoinType fc_jointype,
					  JoinCostWorkspace *fc_workspace,
					  JoinPathExtraData *fc_extra,
					  Path *fc_outer_path,
					  Path *fc_inner_path,
					  List *fc_restrict_clauses,
					  List *fc_pathkeys,
					  Relids fc_required_outer,
					  List *fc_mergeclauses,
					  List *fc_outersortkeys,
					  List *fc_innersortkeys)
{
	MergePath  *fc_pathnode = makeNode(MergePath);

	fc_pathnode->jpath.path.pathtype = T_MergeJoin;
	fc_pathnode->jpath.path.parent = fc_joinrel;
	fc_pathnode->jpath.path.pathtarget = fc_joinrel->reltarget;
	fc_pathnode->jpath.path.param_info =
		get_joinrel_parampathinfo(fc_root,
								  fc_joinrel,
								  fc_outer_path,
								  fc_inner_path,
								  fc_extra->sjinfo,
								  fc_required_outer,
								  &fc_restrict_clauses);
	fc_pathnode->jpath.path.parallel_aware = false;
	fc_pathnode->jpath.path.parallel_safe = fc_joinrel->consider_parallel &&
		fc_outer_path->parallel_safe && fc_inner_path->parallel_safe;
	/* 这是一个愚蠢的方式来估算 parallel_workers，但暂时如此... */
	fc_pathnode->jpath.path.parallel_workers = fc_outer_path->parallel_workers;
	fc_pathnode->jpath.path.pathkeys = fc_pathkeys;
	fc_pathnode->jpath.jointype = fc_jointype;
	fc_pathnode->jpath.inner_unique = fc_extra->inner_unique;
	fc_pathnode->jpath.outerjoinpath = fc_outer_path;
	fc_pathnode->jpath.innerjoinpath = fc_inner_path;
	fc_pathnode->jpath.joinrestrictinfo = fc_restrict_clauses;
	fc_pathnode->path_mergeclauses = fc_mergeclauses;
	fc_pathnode->outersortkeys = fc_outersortkeys;
	fc_pathnode->innersortkeys = fc_innersortkeys;
	/* pathnode->skip_mark_restore 将由 final_cost_mergejoin 设置 */
	/* pathnode->materialize_inner 将由 final_cost_mergejoin 设置 */

	final_cost_mergejoin(fc_root, fc_pathnode, fc_workspace, fc_extra);

	return fc_pathnode;
}

/*
 * create_hashjoin_path
 *	  创建一个对应于两个关系之间哈希连接的路径节点。
 *
 * 'joinrel' 是连接关系
 * 'jointype' 是所需的连接类型
 * 'workspace' 是来自 initial_cost_hashjoin 的结果
 * 'extra' 包含关于连接的各种信息
 * 'outer_path' 是最便宜的外部路径
 * 'inner_path' 是最便宜的内部路径
 * 'parallel_hash' 用于选择内部路径的并行哈希（共享哈希表）
 * 'restrict_clauses' 是要在连接上应用的 RestrictInfo 节点
 * 'required_outer' 是所需的外部关系集
 * 'hashclauses' 是用作哈希子句的 RestrictInfo 节点
 *		（这应该是 restrict_clauses 列表的子集）
 */
HashPath *
create_hashjoin_path(PlannerInfo *fc_root,
					 RelOptInfo *fc_joinrel,
					 JoinType fc_jointype,
					 JoinCostWorkspace *fc_workspace,
					 JoinPathExtraData *fc_extra,
					 Path *fc_outer_path,
					 Path *fc_inner_path,
					 bool fc_parallel_hash,
					 List *fc_restrict_clauses,
					 Relids fc_required_outer,
					 List *fc_hashclauses)
{
	HashPath   *fc_pathnode = makeNode(HashPath);

	fc_pathnode->jpath.path.pathtype = T_HashJoin;
	fc_pathnode->jpath.path.parent = fc_joinrel;
	fc_pathnode->jpath.path.pathtarget = fc_joinrel->reltarget;
	fc_pathnode->jpath.path.param_info =
		get_joinrel_parampathinfo(fc_root,
								  fc_joinrel,
								  fc_outer_path,
								  fc_inner_path,
								  fc_extra->sjinfo,
								  fc_required_outer,
								  &fc_restrict_clauses);
	fc_pathnode->jpath.path.parallel_aware =
		fc_joinrel->consider_parallel && fc_parallel_hash;
	fc_pathnode->jpath.path.parallel_safe = fc_joinrel->consider_parallel &&
		fc_outer_path->parallel_safe && fc_inner_path->parallel_safe;
	/* 这是一个愚蠢的方式来估算 parallel_workers，但暂时如此... */
	fc_pathnode->jpath.path.parallel_workers = fc_outer_path->parallel_workers;

	
/*
	 * 哈希连接永远没有路径键，因为其输出顺序是不可预测的
	 * 可能由于批处理。 XXX 如果内联关系
	 * 足够小，我们可以指示执行器必须不进行批处理，
	 * 然后我们可以假设输出继承外部关系的
	 * 顺序，这可能节省排序步骤。然而，如果我们对内联关系大小的估计严重错误，后果将是相当大的。目前我们不冒这个风险。(还要注意，如果我们真的想认真对待这一点，joinpath.c 将不得不考虑更多的外部关系的路径，而不是现在的情况。)
	 */
	fc_pathnode->jpath.path.pathkeys = NIL;
	fc_pathnode->jpath.jointype = fc_jointype;
	fc_pathnode->jpath.inner_unique = fc_extra->inner_unique;
	fc_pathnode->jpath.outerjoinpath = fc_outer_path;
	fc_pathnode->jpath.innerjoinpath = fc_inner_path;
	fc_pathnode->jpath.joinrestrictinfo = fc_restrict_clauses;
	fc_pathnode->path_hashclauses = fc_hashclauses;
	/* final_cost_hashjoin 将填充 pathnode->num_batches */

	final_cost_hashjoin(fc_root, fc_pathnode, fc_workspace, fc_extra);

	return fc_pathnode;
}

/*
 * create_projection_path
 *	  创建一个表示执行投影的路径节点。
 *
 * 'rel' 是与结果关联的父关系
 * 'subpath' 是表示数据源的路径
 * 'target' 是要计算的 PathTarget
 */
ProjectionPath *
create_projection_path(PlannerInfo *fc_root,
					   RelOptInfo *fc_rel,
					   Path *fc_subpath,
					   PathTarget *fc_target)
{
	ProjectionPath *fc_pathnode = makeNode(ProjectionPath);
	PathTarget *fc_oldtarget;

	/*
	 * 我们不能将一个 ProjectionPath 直接放在另一个上面；这毫无意义
	 * 并且会让 create_projection_plan 感到困惑。与其确保所有
	 * 调用者处理这个问题，不如在这里实现，通过剥离给定内容中的任何
	 * ProjectionPath。根据这个规则，不会有超过一个的情况。
	 */
	if (IsA(fc_subpath, ProjectionPath))
	{
		ProjectionPath *fc_subpp = (ProjectionPath *) fc_subpath;

		Assert(fc_subpp->path.parent == fc_rel);
		fc_subpath = fc_subpp->subpath;
		Assert(!IsA(fc_subpath, ProjectionPath));
	}

	fc_pathnode->path.pathtype = T_Result;
	fc_pathnode->path.parent = fc_rel;
	fc_pathnode->path.pathtarget = fc_target;
	/* 现在假设我们在任何连接的上面，因此没有参数化 */
	fc_pathnode->path.param_info = NULL;
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel &&
		fc_subpath->parallel_safe &&
		is_parallel_safe(fc_root, (Node *) fc_target->exprs);
	fc_pathnode->path.parallel_workers = fc_subpath->parallel_workers;
	/* 投影不改变排序顺序 */
	fc_pathnode->path.pathkeys = fc_subpath->pathkeys;

	fc_pathnode->subpath = fc_subpath;

	/*
	 * 我们可能不需要一个单独的 Result 节点。如果输入计划节点类型
	 * 可以投影，我们可以直接告诉它投影其他内容。或者，如果它
	 * 不能投影，但所需目标与输入将产生的表达式列表相同，我们
	 * 仍然可以给它所需的 tlist（可能改变它的 ressortgroupref 标签，
	 * 但其他不变）。
	 * 注意：在后面的情况下，create_projection_plan 必须重新检查我们的
	 * 结论；见其中的注释。
	 */
	fc_oldtarget = fc_subpath->pathtarget;
	if (is_projection_capable_path(fc_subpath) ||
		equal(fc_oldtarget->exprs, fc_target->exprs))
	{
		/* 不需要单独的 Result 节点 */
		fc_pathnode->dummypp = true;

		/*
		 * 将计划的成本设置为子路径的成本，调整用于 tlist 替换。
		 */
		fc_pathnode->path.rows = fc_subpath->rows;
		fc_pathnode->path.startup_cost = fc_subpath->startup_cost +
			(fc_target->cost.startup - fc_oldtarget->cost.startup);
		fc_pathnode->path.total_cost = fc_subpath->total_cost +
			(fc_target->cost.startup - fc_oldtarget->cost.startup) +
			(fc_target->cost.per_tuple - fc_oldtarget->cost.per_tuple) * fc_subpath->rows;
	}
	else
	{
		/* 我们确实需要 Result 节点 */
		fc_pathnode->dummypp = false;

		/*
		 * Result 节点的成本是每行的 cpu_tuple_cost，加上评估 tlist 的成本。
		 * 不需要担心任何条件。
		 */
		fc_pathnode->path.rows = fc_subpath->rows;
		fc_pathnode->path.startup_cost = fc_subpath->startup_cost +
			fc_target->cost.startup;
		fc_pathnode->path.total_cost = fc_subpath->total_cost +
			fc_target->cost.startup +
			(cpu_tuple_cost + fc_target->cost.per_tuple) * fc_subpath->rows;
	}

	return fc_pathnode;
}

/*
 * apply_projection_to_path
 *	  添加投影步骤，或直接将目标应用于给定路径。
 *
 * 这与 create_projection_path() 的净效应相同，除了如果不需要单独
 * 的 Result 计划节点，我们只需用所需的路径目标替换给定路径的
 * pathtarget。仅当调用者知道给定路径在其他地方没有引用，从而可以
 * 就地修改时，才可以使用此方法。
 *
 * 如果输入路径是 GatherPath 或 GatherMergePath，我们尝试将
 * 新目标向下推送到它的输入；这是对输入路径的进一步侵入式
 * 修改，create_projection_path() 无法做到。
 *
 * 注意，我们不能改变源路径的父链接；因此，当它被 add_path'd 到
 * "rel" 时，事物会有点不一致。到目前为止，这没有造成任何麻烦。
 *
 * 'rel' 是与结果关联的父关系
 * 'path' 是表示数据源的路径
 * 'target' 是要计算的 PathTarget
 */
Path * apply_projection_to_path(PlannerInfo *fc_root,
						 RelOptInfo *fc_rel,
						 Path *fc_path,
						 PathTarget *fc_target)
{
	QualCost	fc_oldcost;

	/*
	 * 如果给定路径无法投影，我们可能需要一个 Result 节点，因此
	 * 制作一个单独的 ProjectionPath。
	 */
	if (!is_projection_capable_path(fc_path))
		return (Path *) create_projection_path(fc_root, fc_rel, fc_path, fc_target);

	/*
	 * 我们可以将所需的 tlist 插入现有路径，但要确保适当地更新其成本估计。
	 */
	fc_oldcost = fc_path->pathtarget->cost;
	fc_path->pathtarget = fc_target;

	fc_path->startup_cost += fc_target->cost.startup - fc_oldcost.startup;
	fc_path->total_cost += fc_target->cost.startup - fc_oldcost.startup +
		(fc_target->cost.per_tuple - fc_oldcost.per_tuple) * fc_path->rows;

	/*
	 * 如果路径恰好是 Gather 或 GatherMerge 路径，我们希望安排
	 * 子路径返回所需的目标列表，以便工作线程可以帮助投影。
	 * 但是，如果目标表达式中有一些不安全的并行性，那么我们就不能这样做。
	 */
	if ((IsA(fc_path, GatherPath) || IsA(fc_path, GatherMergePath)) &&
		is_parallel_safe(fc_root, (Node *) fc_target->exprs))
	{
		/*
		 * 在这里我们始终使用 create_projection_path，即使子路径有
		 * 投影能力，也可以避免就地修改子路径。
		 * 目前看来，不太可能还有任何其他对该子路径的引用，但小心
		 * 驶得万年船。
		 *
		 * 请注意，我们不改变并行路径的成本估计；这样做可能是合适的，
		 * 以反映大部分目标评估将发生在工作线程中的事实。
		 */
		if (IsA(fc_path, GatherPath))
		{
			GatherPath *fc_gpath = (GatherPath *) fc_path;

			fc_gpath->subpath = (Path *)
				create_projection_path(fc_root,
									   fc_gpath->subpath->parent,
									   fc_gpath->subpath,
									   fc_target);
		}
		else
		{
			GatherMergePath *fc_gmpath = (GatherMergePath *) fc_path;

			fc_gmpath->subpath = (Path *)
				create_projection_path(fc_root,
									   fc_gmpath->subpath->parent,
									   fc_gmpath->subpath,
									   fc_target);
		}
	}
	else if (fc_path->parallel_safe &&
			 !is_parallel_safe(fc_root, (Node *) fc_target->exprs))
	{
		/*
		 * 我们正在将一个限制并行的目标列表插入一个当前标记为
		 * 并行安全的路径，因此必须将其标记为不再安全。
		 */
		fc_path->parallel_safe = false;
	}

	return fc_path;
}

/*
 * create_set_projection_path
 *	  创建一个路径节点，表示执行包含集合返回函数的投影。
 *
 * 'rel' 是与结果关联的父关系
 * 'subpath' 是表示数据源的路径
 * 'target' 是要计算的 PathTarget
 */
ProjectSetPath *
create_set_projection_path(PlannerInfo *fc_root,
						   RelOptInfo *fc_rel,
						   Path *fc_subpath,
						   PathTarget *fc_target)
{
	ProjectSetPath *fc_pathnode = makeNode(ProjectSetPath);
	double		fc_tlist_rows;
	ListCell   *fc_lc;

	fc_pathnode->path.pathtype = T_ProjectSet;
	fc_pathnode->path.parent = fc_rel;
	fc_pathnode->path.pathtarget = fc_target;
	/* 现在假设我们在任何连接的上面，因此没有参数化 */
	fc_pathnode->path.param_info = NULL;
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel &&
		fc_subpath->parallel_safe &&
		is_parallel_safe(fc_root, (Node *) fc_target->exprs);
	fc_pathnode->path.parallel_workers = fc_subpath->parallel_workers;
	/* 投影不改变排序顺序 XXX? */
	fc_pathnode->path.pathkeys = fc_subpath->pathkeys;

	fc_pathnode->subpath = fc_subpath;

	/*
	 * 估计每行输入产生 SRFs 的行数；如果这个节点中不止一个，使用最大值。
	 */
	fc_tlist_rows = 1;
	foreach(fc_lc, fc_target->exprs)
	{
		Node	   *fc_node = (Node *) lfirst(fc_lc);
		double		fc_itemrows;

		fc_itemrows = expression_returns_set_rows(fc_root, fc_node);
		if (fc_tlist_rows < fc_itemrows)
			fc_tlist_rows = fc_itemrows;
	}

	/*
	 * 除了评估 tlist 的成本外，每行输入收取 cpu_tuple_cost，
	 * 对于每个新增的输出行收取一半的 cpu_tuple_cost。
	 * 这可能有点奇怪，但这正是 9.6 所做的；我们可能会在稍后重新审视
	 * 这个估计。
	 */
	fc_pathnode->path.rows = fc_subpath->rows * fc_tlist_rows;
	fc_pathnode->path.startup_cost = fc_subpath->startup_cost +
		fc_target->cost.startup;
	fc_pathnode->path.total_cost = fc_subpath->total_cost +
		fc_target->cost.startup +
		(cpu_tuple_cost + fc_target->cost.per_tuple) * fc_subpath->rows +
		(fc_pathnode->path.rows - fc_subpath->rows) * cpu_tuple_cost / 2;

	return fc_pathnode;
}

/*
 * create_incremental_sort_path
 *	  创建一个路径节点，表示执行增量排序。
 *
 * 'rel' 是与结果关联的父关系
 * 'subpath' 是表示数据源的路径
 * 'pathkeys' 表示所需的排序顺序
 * 'presorted_keys' 是输入路径已排序的键数
 * 'limit_tuples' 是输出元组数量的估计限制，
 *		或者 -1 如果没有 LIMIT 或无法估计
 */
IncrementalSortPath *
create_incremental_sort_path(PlannerInfo *fc_root,
							 RelOptInfo *fc_rel,
							 Path *fc_subpath,
							 List *fc_pathkeys,
							 int fc_presorted_keys,
							 double fc_limit_tuples)
{
	IncrementalSortPath *fc_sort = makeNode(IncrementalSortPath);
	SortPath   *fc_pathnode = &fc_sort->spath;

	fc_pathnode->path.pathtype = T_IncrementalSort;
	fc_pathnode->path.parent = fc_rel;
	/* 排序不投影，因此使用源路径的 pathtarget */
	fc_pathnode->path.pathtarget = fc_subpath->pathtarget;
	/* 现在假设我们在任何连接的上面，因此没有参数化 */
	fc_pathnode->path.param_info = NULL;
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel &&
		fc_subpath->parallel_safe;
	fc_pathnode->path.parallel_workers = fc_subpath->parallel_workers;
	fc_pathnode->path.pathkeys = fc_pathkeys;

	fc_pathnode->subpath = fc_subpath;

	cost_incremental_sort(&fc_pathnode->path,
						  fc_root, fc_pathkeys, fc_presorted_keys,
						  fc_subpath->startup_cost,
						  fc_subpath->total_cost,
						  fc_subpath->rows,
						  fc_subpath->pathtarget->width,
						  0.0,	/* XXX comparison_cost 不应该为 0？ */
						  work_mem, fc_limit_tuples);

	fc_sort->nPresortedCols = fc_presorted_keys;

	return fc_sort;
}

/*
 * create_sort_path
 *	  创建一个表示执行显式排序的路径节点。
 *
 * 'rel' 是与结果关联的父关系
 * 'subpath' 是表示数据源的路径
 * 'pathkeys' 代表所需的排序顺序
 * 'limit_tuples' 是对输出元组数量的估计上限，
 *		如果没有 LIMIT 或无法估计，则为 -1
 */
SortPath *
create_sort_path(PlannerInfo *fc_root,
				 RelOptInfo *fc_rel,
				 Path *fc_subpath,
				 List *fc_pathkeys,
				 double fc_limit_tuples)
{
	SortPath   *fc_pathnode = makeNode(SortPath);

	fc_pathnode->path.pathtype = T_Sort;
	fc_pathnode->path.parent = fc_rel;
	/* 排序不投影，因此使用源路径的 pathtarget */
	fc_pathnode->path.pathtarget = fc_subpath->pathtarget;
	/* 现在假设我们在任何连接的上面，因此没有参数化 */
	fc_pathnode->path.param_info = NULL;
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel &&
		fc_subpath->parallel_safe;
	fc_pathnode->path.parallel_workers = fc_subpath->parallel_workers;
	fc_pathnode->path.pathkeys = fc_pathkeys;

	fc_pathnode->subpath = fc_subpath;

	cost_sort(&fc_pathnode->path, fc_root, fc_pathkeys,
			  fc_subpath->total_cost,
			  fc_subpath->rows,
			  fc_subpath->pathtarget->width,
			  0.0,				/* XXX comparison_cost 不应该为 0？ */
			  work_mem, fc_limit_tuples);

	return fc_pathnode;
}

/*
 * create_group_path
 *	  创建一个表示对预排序输入进行分组的路径节点
 *
 * 'rel' 是与结果关联的父关系
 * 'subpath' 是表示数据源的路径
 * 'target' 是要计算的 PathTarget
 * 'groupClause' 是代表分组的 SortGroupClause 列表
 * 'qual' 是 HAVING 约束（如果有的话）
 * 'numGroups' 是估计的组数
 */
GroupPath *
create_group_path(PlannerInfo *fc_root,
				  RelOptInfo *fc_rel,
				  Path *fc_subpath,
				  List *fc_groupClause,
				  List *fc_qual,
				  double fc_numGroups)
{
	GroupPath  *fc_pathnode = makeNode(GroupPath);
	PathTarget *fc_target = fc_rel->reltarget;

	fc_pathnode->path.pathtype = T_Group;
	fc_pathnode->path.parent = fc_rel;
	fc_pathnode->path.pathtarget = fc_target;
	/* 现在假设我们在任何连接的上面，因此没有参数化 */
	fc_pathnode->path.param_info = NULL;
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel &&
		fc_subpath->parallel_safe;
	fc_pathnode->path.parallel_workers = fc_subpath->parallel_workers;
	/* 分组不改变排序顺序 */
	fc_pathnode->path.pathkeys = fc_subpath->pathkeys;

	fc_pathnode->subpath = fc_subpath;

	fc_pathnode->groupClause = fc_groupClause;
	fc_pathnode->qual = fc_qual;

	cost_group(&fc_pathnode->path, fc_root,
			   list_length(fc_groupClause),
			   fc_numGroups,
			   fc_qual,
			   fc_subpath->startup_cost, fc_subpath->total_cost,
			   fc_subpath->rows);

	/* 为每个输出行添加 tlist 评估成本 */
	fc_pathnode->path.startup_cost += fc_target->cost.startup;
	fc_pathnode->path.total_cost += fc_target->cost.startup +
		fc_target->cost.per_tuple * fc_pathnode->path.rows;

	return fc_pathnode;
}

/*
 * create_upper_unique_path
 *	  创建一个表示对预排序输入执行显式 Unique 步骤的路径节点。
 *
 * 这会生成一个 Unique 计划节点，但用例与
 * create_unique_path 非常不同，因此合并这两个似乎不值得。
 *
 * 'rel' 是与结果关联的父关系
 * 'subpath' 是表示数据源的路径
 * 'numCols' 是分组列的数量
 * 'numGroups' 是估计的组数
 *
 * 输入路径必须在分组列上排序，再加上可能的
 * 其他列；因此前 numCols 个 pathkeys 是分组列
 */
UpperUniquePath *
create_upper_unique_path(PlannerInfo *fc_root,
						 RelOptInfo *fc_rel,
						 Path *fc_subpath,
						 int fc_numCols,
						 double fc_numGroups)
{
	UpperUniquePath *fc_pathnode = makeNode(UpperUniquePath);

	fc_pathnode->path.pathtype = T_Unique;
	fc_pathnode->path.parent = fc_rel;
	/* Unique 不进行投影，因此使用源路径的 pathtarget */
	fc_pathnode->path.pathtarget = fc_subpath->pathtarget;
	/* 现在假设我们在任何连接的上面，因此没有参数化 */
	fc_pathnode->path.param_info = NULL;
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel &&
		fc_subpath->parallel_safe;
	fc_pathnode->path.parallel_workers = fc_subpath->parallel_workers;
	/* Unique 不改变输入排序 */
	fc_pathnode->path.pathkeys = fc_subpath->pathkeys;

	fc_pathnode->subpath = fc_subpath;
	fc_pathnode->numkeys = fc_numCols;

	/*
	 * 每个输入元组每次比较收取一个 cpu_operator_cost。我们假设
	 * 所有列在最多的元组中比较。 （XXX 这可能是一个高估。）
	 */
	fc_pathnode->path.startup_cost = fc_subpath->startup_cost;
	fc_pathnode->path.total_cost = fc_subpath->total_cost +
		cpu_operator_cost * fc_subpath->rows * fc_numCols;
	fc_pathnode->path.rows = fc_numGroups;

	return fc_pathnode;
}

/*
 * create_agg_path
 *	  创建一个表示进行聚合/分组的路径节点
 *
 * 'rel' 是与结果关联的父关系
 * 'subpath' 是表示数据源的路径
 * 'target' 是要计算的 PathTarget
 * 'aggstrategy' 是 Agg 节点的基本实现策略
 * 'aggsplit' 是 Agg 节点的聚合分割模式
 * 'groupClause' 是代表分组的 SortGroupClause 列表
 * 'qual' 是 HAVING 约束（如果有的话）
 * 'aggcosts' 包含有关要计算的聚合函数的成本信息
 * 'numGroups' 是估计的组数（如果不分组则为 1）
 */
AggPath *
create_agg_path(PlannerInfo *fc_root,
				RelOptInfo *fc_rel,
				Path *fc_subpath,
				PathTarget *fc_target,
				AggStrategy fc_aggstrategy,
				AggSplit fc_aggsplit,
				List *fc_groupClause,
				List *fc_qual,
				const AggClauseCosts *fc_aggcosts,
				double fc_numGroups)
{
	AggPath    *fc_pathnode = makeNode(AggPath);

	fc_pathnode->path.pathtype = T_Agg;
	fc_pathnode->path.parent = fc_rel;
	fc_pathnode->path.pathtarget = fc_target;
	/* 现在假设我们在任何连接的上面，因此没有参数化 */
	fc_pathnode->path.param_info = NULL;
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel &&
		fc_subpath->parallel_safe;
	fc_pathnode->path.parallel_workers = fc_subpath->parallel_workers;
	if (fc_aggstrategy == AGG_SORTED)
		fc_pathnode->path.pathkeys = fc_subpath->pathkeys;	/* 保留顺序 */
	else
		fc_pathnode->path.pathkeys = NIL;	/* 输出是无序的 */
	fc_pathnode->subpath = fc_subpath;

	fc_pathnode->aggstrategy = fc_aggstrategy;
	fc_pathnode->aggsplit = fc_aggsplit;
	fc_pathnode->numGroups = fc_numGroups;
	fc_pathnode->transitionSpace = fc_aggcosts ? fc_aggcosts->transitionSpace : 0;
	fc_pathnode->groupClause = fc_groupClause;
	fc_pathnode->qual = fc_qual;

	cost_agg(&fc_pathnode->path, fc_root,
			 fc_aggstrategy, fc_aggcosts,
			 list_length(fc_groupClause), fc_numGroups,
			 fc_qual,
			 fc_subpath->startup_cost, fc_subpath->total_cost,
			 fc_subpath->rows, fc_subpath->pathtarget->width);

	/* 为每个输出行添加 tlist 评估成本 */
	fc_pathnode->path.startup_cost += fc_target->cost.startup;
	fc_pathnode->path.total_cost += fc_target->cost.startup +
		fc_target->cost.per_tuple * fc_pathnode->path.rows;

	return fc_pathnode;
}

/*
 * create_groupingsets_path
 *	  创建一个表示执行 GROUPING SETS 聚合的路径节点
 *
 * GroupingSetsPath 表示带有一个或多个分组集的排序分组。
 * 输入路径的结果必须经过排序，以匹配 rollup_groupclauses 中的最后一个条目。
 *
 * 'rel' 是与结果相关的父关系
 * 'subpath' 是表示数据源的路径
 * 'target' 是要计算的 PathTarget
 * 'having_qual' 是 HAVING quals（如果有的话）
 * 'rollups' 是 RollupData 节点的列表
 * 'agg_costs' 包含有关要计算的聚合函数的成本信息
 * 'numGroups' 是估计的总组数
 */
GroupingSetsPath *
create_groupingsets_path(PlannerInfo *fc_root,
						 RelOptInfo *fc_rel,
						 Path *fc_subpath,
						 List *fc_having_qual,
						 AggStrategy fc_aggstrategy,
						 List *fc_rollups,
						 const AggClauseCosts *fc_agg_costs,
						 double fc_numGroups)
{
	GroupingSetsPath *fc_pathnode = makeNode(GroupingSetsPath);
	PathTarget *fc_target = fc_rel->reltarget;
	ListCell   *fc_lc;
	bool		fc_is_first = true;
	bool		fc_is_first_sort = true;

	/* 生成的最上层计划节点将是 Agg */
	fc_pathnode->path.pathtype = T_Agg;
	fc_pathnode->path.parent = fc_rel;
	fc_pathnode->path.pathtarget = fc_target;
	fc_pathnode->path.param_info = fc_subpath->param_info;
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel &&
		fc_subpath->parallel_safe;
	fc_pathnode->path.parallel_workers = fc_subpath->parallel_workers;
	fc_pathnode->subpath = fc_subpath;

	/*
	 * 如果可能，简化调用者，通过将 AGG_SORTED 降级为 AGG_PLAIN，并将 AGG_MIXED 降级为 AGG_HASHED。
	 */
	if (fc_aggstrategy == AGG_SORTED &&
		list_length(fc_rollups) == 1 &&
		((RollupData *) linitial(fc_rollups))->groupClause == NIL)
		fc_aggstrategy = AGG_PLAIN;

	if (fc_aggstrategy == AGG_MIXED &&
		list_length(fc_rollups) == 1)
		fc_aggstrategy = AGG_HASHED;

	/*
	 * 输出将按 group_pathkeys 的排序顺序排列，仅当对非空分组
	 * 表达式进行单个 rollup 操作时才会如此。
	 */
	if (fc_aggstrategy == AGG_SORTED && list_length(fc_rollups) == 1)
		fc_pathnode->path.pathkeys = fc_root->group_pathkeys;
	else
		fc_pathnode->path.pathkeys = NIL;

	fc_pathnode->aggstrategy = fc_aggstrategy;
	fc_pathnode->rollups = fc_rollups;
	fc_pathnode->qual = fc_having_qual;
	fc_pathnode->transitionSpace = fc_agg_costs ? fc_agg_costs->transitionSpace : 0;

	Assert(fc_rollups != NIL);
	Assert(fc_aggstrategy != AGG_PLAIN || list_length(fc_rollups) == 1);
	Assert(fc_aggstrategy != AGG_MIXED || list_length(fc_rollups) > 1);

	foreach(fc_lc, fc_rollups)
	{
		RollupData *fc_rollup = lfirst(fc_lc);
		List	   *fc_gsets = fc_rollup->gsets;
		int			fc_numGroupCols = list_length(linitial(fc_gsets));

		/*
		 * 在 AGG_SORTED 或 AGG_PLAIN 模式下，第一个 rollup 采用
		 * （已经排序的）输入，后续的进行自己的排序。
		 *
		 * 在 AGG_HASHED 模式下，每个分组集都有一个 rollup。
		 *
		 * 在 AGG_MIXED 模式下，第一个 rollup 是哈希的，第一个
		 * 非哈希的将采用（已经排序的）输入，后续的进行自己的排序。
		 */
		if (fc_is_first)
		{
			cost_agg(&fc_pathnode->path, fc_root,
					 fc_aggstrategy,
					 fc_agg_costs,
					 fc_numGroupCols,
					 fc_rollup->numGroups,
					 fc_having_qual,
					 fc_subpath->startup_cost,
					 fc_subpath->total_cost,
					 fc_subpath->rows,
					 fc_subpath->pathtarget->width);
			fc_is_first = false;
			if (!fc_rollup->is_hashed)
				fc_is_first_sort = false;
		}
		else
		{
			Path		sort_path;	/* cost_sort 结果的占位符 */
			Path		fc_agg_path;	/* 成本聚合结果的虚拟对象 */

			if (fc_rollup->is_hashed || fc_is_first_sort)
			{
				/*
				 * 考虑聚合的成本，但不要再收取输入成本
				 */
				cost_agg(&fc_agg_path, fc_root,
						 fc_rollup->is_hashed ? AGG_HASHED : AGG_SORTED,
						 fc_agg_costs,
						 fc_numGroupCols,
						 fc_rollup->numGroups,
						 fc_having_qual,
						 0.0, 0.0,
						 fc_subpath->rows,
						 fc_subpath->pathtarget->width);
				if (!fc_rollup->is_hashed)
					fc_is_first_sort = false;
			}
			else
			{
				/* 考虑排序的成本，但不要再收取输入成本 */
				cost_sort(&sort_path, fc_root, NIL,
						  0.0,
						  fc_subpath->rows,
						  fc_subpath->pathtarget->width,
						  0.0,
						  work_mem,
						  -1.0);

				/* 考虑聚合的成本 */

				cost_agg(&fc_agg_path, fc_root,
						 AGG_SORTED,
						 fc_agg_costs,
						 fc_numGroupCols,
						 fc_rollup->numGroups,
						 fc_having_qual,
						 sort_path.startup_cost,
						 sort_path.total_cost,
						 sort_path.rows,
						 fc_subpath->pathtarget->width);
			}

			fc_pathnode->path.total_cost += fc_agg_path.total_cost;
			fc_pathnode->path.rows += fc_agg_path.rows;
		}
	}

	/* 为每个输出行添加 tlist 评估成本 */
	fc_pathnode->path.startup_cost += fc_target->cost.startup;
	fc_pathnode->path.total_cost += fc_target->cost.startup +
		fc_target->cost.per_tuple * fc_pathnode->path.rows;

	return fc_pathnode;
}

/*
 * create_minmaxagg_path
 *	  创建一个表示计算 MIN/MAX 聚合的路径节点
 *
 * 'rel' 是与结果相关的父关系
 * 'target' 是要计算的 PathTarget
 * 'mmaggregates' 是 MinMaxAggInfo 结构的列表
 * 'quals' 是 HAVING quals（如果有的话）
 */
MinMaxAggPath *
create_minmaxagg_path(PlannerInfo *fc_root,
					  RelOptInfo *fc_rel,
					  PathTarget *fc_target,
					  List *fc_mmaggregates,
					  List *fc_quals)
{
	MinMaxAggPath *fc_pathnode = makeNode(MinMaxAggPath);
	Cost		fc_initplan_cost;
	ListCell   *fc_lc;

	/* 生成的最上层计划节点将是 Result */
	fc_pathnode->path.pathtype = T_Result;
	fc_pathnode->path.parent = fc_rel;
	fc_pathnode->path.pathtarget = fc_target;
	/* 现在假设我们在任何连接的上面，因此没有参数化 */
	fc_pathnode->path.param_info = NULL;
	fc_pathnode->path.parallel_aware = false;
	/* MinMaxAggPath 意味着使用 initplans，因此不能是并行安全的 */
	fc_pathnode->path.parallel_safe = false;
	fc_pathnode->path.parallel_workers = 0;
	/* Result 是一行无序数据 */
	fc_pathnode->path.rows = 1;
	fc_pathnode->path.pathkeys = NIL;

	fc_pathnode->mmaggregates = fc_mmaggregates;
	fc_pathnode->quals = fc_quals;

	/* 计算所有 initplans 的成本 ... */
	fc_initplan_cost = 0;
	foreach(fc_lc, fc_mmaggregates)
	{
		MinMaxAggInfo *fc_mminfo = (MinMaxAggInfo *) lfirst(fc_lc);

		fc_initplan_cost += fc_mminfo->pathcost;
	}

	/* 为每个输出行添加 tlist 评估成本，加上 cpu_tuple_cost */
	fc_pathnode->path.startup_cost = fc_initplan_cost + fc_target->cost.startup;
	fc_pathnode->path.total_cost = fc_initplan_cost + fc_target->cost.startup +
		fc_target->cost.per_tuple + cpu_tuple_cost;

	/*
	 * 如果有qual，则添加qual的成本——但是我们忽略其选择性，因为我们的
	 * 行数估计应该是1，无论qual是什么。
	 */
	if (fc_quals)
	{
		QualCost	fc_qual_cost;

		cost_qual_eval(&fc_qual_cost, fc_quals, fc_root);
		fc_pathnode->path.startup_cost += fc_qual_cost.startup;
		fc_pathnode->path.total_cost += fc_qual_cost.startup + fc_qual_cost.per_tuple;
	}

	return fc_pathnode;
}

/*
 * create_windowagg_path
 *	  创建一个表示计算窗口函数的路径节点
 *
 * 'rel' 是与结果相关的父关系
 * 'subpath' 是表示数据源的路径
 * 'target' 是要计算的 PathTarget
 * 'windowFuncs' 是 WindowFunc 结构的列表
 * 'winclause' 是所有 WindowFuncs 共有的 WindowClause
 * 'qual' 是来自更低级 WindowAggPaths 的 WindowClause.runconditions。
 *		当 topwindow == false 时，必须始终为 NIL
 * 'topwindow' 仅在顶级 WindowAgg 时设置为 true，所有
 *		中间 WindowAgg 设置为 false。
 *
 * 输入必须按照 WindowClause 的 PARTITION 键和 ORDER BY 键进行排序。
 */
WindowAggPath *
create_windowagg_path(PlannerInfo *fc_root,
					  RelOptInfo *fc_rel,
					  Path *fc_subpath,
					  PathTarget *fc_target,
					  List *fc_windowFuncs,
					  WindowClause *fc_winclause,
					  List *fc_qual,
					  bool fc_topwindow)
{
	WindowAggPath *fc_pathnode = makeNode(WindowAggPath);

	/* 只有在 topwindow 时才能设置 qual */
	Assert(fc_qual == NIL || fc_topwindow);

	fc_pathnode->path.pathtype = T_WindowAgg;
	fc_pathnode->path.parent = fc_rel;
	fc_pathnode->path.pathtarget = fc_target;
	/* 现在假设我们在任何连接的上面，因此没有参数化 */
	fc_pathnode->path.param_info = NULL;
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel &&
		fc_subpath->parallel_safe;
	fc_pathnode->path.parallel_workers = fc_subpath->parallel_workers;
	/* WindowAgg 保留输入的排序顺序 */
	fc_pathnode->path.pathkeys = fc_subpath->pathkeys;

	fc_pathnode->subpath = fc_subpath;
	fc_pathnode->winclause = fc_winclause;
	fc_pathnode->qual = fc_qual;
	fc_pathnode->topwindow = fc_topwindow;

	/*
	 * 在成本计算中，假设没有冗余的分区
	 * 或排序列；为了处理这个边缘案例，没必要费心。
	 * 所以我们只是将未修改的列表长度传递给
	 * cost_windowagg。
	 */
	cost_windowagg(&fc_pathnode->path, fc_root,
				   fc_windowFuncs,
				   list_length(fc_winclause->partitionClause),
				   list_length(fc_winclause->orderClause),
				   fc_subpath->startup_cost,
				   fc_subpath->total_cost,
				   fc_subpath->rows);

	/* 为每个输出行添加 tlist 评估成本 */
	fc_pathnode->path.startup_cost += fc_target->cost.startup;
	fc_pathnode->path.total_cost += fc_target->cost.startup +
		fc_target->cost.per_tuple * fc_pathnode->path.rows;

	return fc_pathnode;
}

/*
 * create_setop_path
 *	  创建一个表示计算 INTERSECT 或 EXCEPT 的路径节点
 *
 * 'rel' 是与结果相关的父关系
 * 'subpath' 是表示数据源的路径
 * 'cmd' 是特定的语义（INTERSECT 或 EXCEPT，是否包含 ALL）
 * 'strategy' 是实现策略（排序或哈希）
 * 'distinctList' 是表示分组的 SortGroupClause 列表
 * 'flagColIdx' 是标志列所在的列号（如果有）
 * 'firstFlag' 是在哈希时第一个输入关系的标志值；
 *		排序时为 -1
 * 'numGroups' 是估计的不同组数
 * 'outputRows' 是估计的输出行数
 */
SetOpPath *
create_setop_path(PlannerInfo *fc_root,
				  RelOptInfo *fc_rel,
				  Path *fc_subpath,
				  SetOpCmd fc_cmd,
				  SetOpStrategy fc_strategy,
				  List *fc_distinctList,
				  AttrNumber fc_flagColIdx,
				  int fc_firstFlag,
				  double fc_numGroups,
				  double fc_outputRows)
{
	SetOpPath  *fc_pathnode = makeNode(SetOpPath);

	fc_pathnode->path.pathtype = T_SetOp;
	fc_pathnode->path.parent = fc_rel;
	/* SetOp 不进行投影，因此使用源路径的 pathtarget */
	fc_pathnode->path.pathtarget = fc_subpath->pathtarget;
	/* 现在假设我们在任何连接的上面，因此没有参数化 */
	fc_pathnode->path.param_info = NULL;
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel &&
		fc_subpath->parallel_safe;
	fc_pathnode->path.parallel_workers = fc_subpath->parallel_workers;
	/* 如果处于排序模式，SetOp 会保留输入的排序顺序 */
	fc_pathnode->path.pathkeys =
		(fc_strategy == SETOP_SORTED) ? fc_subpath->pathkeys : NIL;

	fc_pathnode->subpath = fc_subpath;
	fc_pathnode->cmd = fc_cmd;
	fc_pathnode->strategy = fc_strategy;
	fc_pathnode->distinctList = fc_distinctList;
	fc_pathnode->flagColIdx = fc_flagColIdx;
	fc_pathnode->firstFlag = fc_firstFlag;
	fc_pathnode->numGroups = fc_numGroups;

	/*
	 * 对每个输入元组的每次比较收取一个 cpu_operator_cost。我们假设所有列在大多数元组中都进行比较。
	 */
	fc_pathnode->path.startup_cost = fc_subpath->startup_cost;
	fc_pathnode->path.total_cost = fc_subpath->total_cost +
		cpu_operator_cost * fc_subpath->rows * list_length(fc_distinctList);
	fc_pathnode->path.rows = fc_outputRows;

	return fc_pathnode;
}

/*
 * create_recursiveunion_path
 *	  创建一个表示递归 UNION 节点的路径节点
 *
 * 'rel' 是与结果相关的父关系
 * 'leftpath' 是非递归项的数据源
 * 'rightpath' 是递归项的数据源
 * 'target' 是要计算的 PathTarget
 * 'distinctList' 是表示分组的 SortGroupClause 列表
 * 'wtParam' 是表示工作表的 Param 的 ID
 * 'numGroups' 是估计的组数
 *
 * 对于递归 UNION ALL，distinctList 为空，numGroups 为零
 */
RecursiveUnionPath *
create_recursiveunion_path(PlannerInfo *fc_root,
						   RelOptInfo *fc_rel,
						   Path *fc_leftpath,
						   Path *fc_rightpath,
						   PathTarget *fc_target,
						   List *fc_distinctList,
						   int fc_wtParam,
						   double fc_numGroups)
{
	RecursiveUnionPath *fc_pathnode = makeNode(RecursiveUnionPath);

	fc_pathnode->path.pathtype = T_RecursiveUnion;
	fc_pathnode->path.parent = fc_rel;
	fc_pathnode->path.pathtarget = fc_target;
	/* 现在假设我们在任何连接的上面，因此没有参数化 */
	fc_pathnode->path.param_info = NULL;
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel &&
		fc_leftpath->parallel_safe && fc_rightpath->parallel_safe;
	/* 愚蠢，但我们将像连接一样处理它： */
	fc_pathnode->path.parallel_workers = fc_leftpath->parallel_workers;
	/* RecursiveUnion 的结果始终是无序的 */
	fc_pathnode->path.pathkeys = NIL;

	fc_pathnode->leftpath = fc_leftpath;
	fc_pathnode->rightpath = fc_rightpath;
	fc_pathnode->distinctList = fc_distinctList;
	fc_pathnode->wtParam = fc_wtParam;
	fc_pathnode->numGroups = fc_numGroups;

	cost_recursive_union(&fc_pathnode->path, fc_leftpath, fc_rightpath);

	return fc_pathnode;
}


/*
 * create_lockrows_path
 *	  创建一个表示获取行锁的路径节点
 *
 * 'rel' 是与结果相关的父关系
 * 'subpath' 是表示数据源的路径
 * 'rowMarks' 是 PlanRowMark 的列表
 * 'epqParam' 是 EvalPlanQual 重新评估的参数 ID
 */
LockRowsPath *
create_lockrows_path(PlannerInfo *fc_root, RelOptInfo *fc_rel,
					 Path *fc_subpath, List *fc_rowMarks, int fc_epqParam)
{
	LockRowsPath *fc_pathnode = makeNode(LockRowsPath);

	fc_pathnode->path.pathtype = T_LockRows;
	fc_pathnode->path.parent = fc_rel;
	/* LockRows 不进行投影，因此使用源路径的 pathtarget */
	fc_pathnode->path.pathtarget = fc_subpath->pathtarget;
	/* 现在假设我们在任何连接的上面，因此没有参数化 */
	fc_pathnode->path.param_info = NULL;
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = false;
	fc_pathnode->path.parallel_workers = 0;
	fc_pathnode->path.rows = fc_subpath->rows;

	/*
	 * 结果不能假定已排序，因为锁定可能会导致排序
	 * 键列被新值替换。
	 */
	fc_pathnode->path.pathkeys = NIL;

	fc_pathnode->subpath = fc_subpath;
	fc_pathnode->rowMarks = fc_rowMarks;
	fc_pathnode->epqParam = fc_epqParam;

	/*
	 * 我们应该为行锁定和可能的重新获取收取一些额外费用，但很难说有多少。 目前，按每行使用 cpu_tuple_cost。
	 */
	fc_pathnode->path.startup_cost = fc_subpath->startup_cost;
	fc_pathnode->path.total_cost = fc_subpath->total_cost +
		cpu_tuple_cost * fc_subpath->rows;

	return fc_pathnode;
}

/*
 * create_modifytable_path
 *	  创建一个表示执行 INSERT/UPDATE/DELETE/MERGE
 *	  修改的路径节点
 *
 * 'rel' 是与结果相关的父关系
 * 'subpath' 是生成源数据的路径
 * 'operation' 是操作类型
 * 'canSetTag' 如果我们设置命令标签/es_processed 则为 true
 * 'nominalRelation' 是用于 EXPLAIN 的父 RT 索引
 * 'rootRelation' 是分区/继承表的根 RTI，如果没有则为 0
 * 'partColsUpdated' 如果正在更新任何分区列，则为 true，
 *		无论是来自目标关系还是后代分区表。
 * 'resultRelations' 是目标关系的实际 RT 索引的整数列表
 * 'updateColnosLists' 是 UPDATE 目标列编号列表的列表
 *		（每个关系一个子列表）；如果不是 UPDATE，则为 NIL
 * 'withCheckOptionLists' 是 WCO 列表的列表（每个关系一个）
 * 'returningLists' 是 RETURNING tlists 的列表（每个关系一个）
 * 'rowMarks' 是 PlanRowMarks 的列表（仅非锁定）
 * 'onconflict' 是 ON CONFLICT 子句，或 NULL
 * 'epqParam' 是 EvalPlanQual 重新评估的参数 ID
 * 'mergeActionLists' 是 MERGE 操作的列表列表（每个关系一个）
 */
ModifyTablePath *
create_modifytable_path(PlannerInfo *fc_root, RelOptInfo *fc_rel,
						Path *fc_subpath,
						CmdType fc_operation, bool fc_canSetTag,
						Index fc_nominalRelation, Index fc_rootRelation,
						bool fc_partColsUpdated,
						List *fc_resultRelations,
						List *fc_updateColnosLists,
						List *fc_withCheckOptionLists, List *fc_returningLists,
						List *fc_rowMarks, OnConflictExpr *fc_onconflict,
						List *fc_mergeActionLists, int fc_epqParam)
{
	ModifyTablePath *fc_pathnode = makeNode(ModifyTablePath);

	Assert(fc_operation == CMD_MERGE ||
		   (fc_operation == CMD_UPDATE ?
			list_length(fc_resultRelations) == list_length(fc_updateColnosLists) :
			fc_updateColnosLists == NIL));
	Assert(fc_withCheckOptionLists == NIL ||
		   list_length(fc_resultRelations) == list_length(fc_withCheckOptionLists));
	Assert(fc_returningLists == NIL ||
		   list_length(fc_resultRelations) == list_length(fc_returningLists));

	fc_pathnode->path.pathtype = T_ModifyTable;
	fc_pathnode->path.parent = fc_rel;
	/* pathtarget 不有趣，仅使其最小有效 */
	fc_pathnode->path.pathtarget = fc_rel->reltarget;
	/* 现在假设我们在任何连接的上面，因此没有参数化 */
	fc_pathnode->path.param_info = NULL;
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = false;
	fc_pathnode->path.parallel_workers = 0;
	fc_pathnode->path.pathkeys = NIL;

	/*
	 * 计算成本和行数作为子路径的成本和行数（如果有RETURNING）
	 *
	 * 目前，我们不会额外收费实际的表修改工作，也不会对WITH CHECK OPTIONS或RETURNING表达式（如果有的话）收费。 这只是窗饰，因为ModifyTable始终是一个顶级节点，成本无法改变任何更高层次的规划选择。但我们可能想在某个时候让它看起来更好。
	 */
	fc_pathnode->path.startup_cost = fc_subpath->startup_cost;
	fc_pathnode->path.total_cost = fc_subpath->total_cost;
	if (fc_returningLists != NIL)
	{
		fc_pathnode->path.rows = fc_subpath->rows;

		/*
		 * 设置宽度以匹配子路径的输出。 XXX 这完全错误：
		 * 我们应该返回RETURNING tlist宽度的平均值。但这是历史上发生的事情，改善它是另一天的任务。（再说一次，这主要是窗饰。）
		 */
		fc_pathnode->path.pathtarget->width = fc_subpath->pathtarget->width;
	}
	else
	{
		fc_pathnode->path.rows = 0;
		fc_pathnode->path.pathtarget->width = 0;
	}

	fc_pathnode->subpath = fc_subpath;
	fc_pathnode->operation = fc_operation;
	fc_pathnode->canSetTag = fc_canSetTag;
	fc_pathnode->nominalRelation = fc_nominalRelation;
	fc_pathnode->rootRelation = fc_rootRelation;
	fc_pathnode->partColsUpdated = fc_partColsUpdated;
	fc_pathnode->resultRelations = fc_resultRelations;
	fc_pathnode->updateColnosLists = fc_updateColnosLists;
	fc_pathnode->withCheckOptionLists = fc_withCheckOptionLists;
	fc_pathnode->returningLists = fc_returningLists;
	fc_pathnode->rowMarks = fc_rowMarks;
	fc_pathnode->onconflict = fc_onconflict;
	fc_pathnode->epqParam = fc_epqParam;
	fc_pathnode->mergeActionLists = fc_mergeActionLists;

	return fc_pathnode;
}

/*
 * create_limit_path
 *	  创建一个表示执行LIMIT/OFFSET的pathnode
 *
 * 除了提供实际的OFFSET和LIMIT表达式，调用者还必须提供它们的值估计以便进行成本计算。 这些估计是由preprocess_limit()计算的，即0表示子句不存在，-1表示存在但我们无法估计其值。
 *
 * 'rel'是与结果相关的父关系
 * 'subpath'是表示数据源的路径
 * 'limitOffset'是实际的OFFSET表达式，或NULL
 * 'limitCount'是实际的LIMIT表达式，或NULL
 * 'offset_est'是OFFSET表达式的估计值
 * 'count_est'是LIMIT表达式的估计值
 */
LimitPath *
create_limit_path(PlannerInfo *fc_root, RelOptInfo *fc_rel,
				  Path *fc_subpath,
				  Node *fc_limitOffset, Node *fc_limitCount,
				  LimitOption fc_limitOption,
				  int64 fc_offset_est, int64 fc_count_est)
{
	LimitPath  *fc_pathnode = makeNode(LimitPath);

	fc_pathnode->path.pathtype = T_Limit;
	fc_pathnode->path.parent = fc_rel;
	/* Limit不进行投影，因此使用源路径的pathtarget */
	fc_pathnode->path.pathtarget = fc_subpath->pathtarget;
	/* 现在假设我们在任何连接的上面，因此没有参数化 */
	fc_pathnode->path.param_info = NULL;
	fc_pathnode->path.parallel_aware = false;
	fc_pathnode->path.parallel_safe = fc_rel->consider_parallel &&
		fc_subpath->parallel_safe;
	fc_pathnode->path.parallel_workers = fc_subpath->parallel_workers;
	fc_pathnode->path.rows = fc_subpath->rows;
	fc_pathnode->path.startup_cost = fc_subpath->startup_cost;
	fc_pathnode->path.total_cost = fc_subpath->total_cost;
	fc_pathnode->path.pathkeys = fc_subpath->pathkeys;
	fc_pathnode->subpath = fc_subpath;
	fc_pathnode->limitOffset = fc_limitOffset;
	fc_pathnode->limitCount = fc_limitCount;
	fc_pathnode->limitOption = fc_limitOption;

	/*
	 * 根据offset/limit调整输出行数和成本。
	 */
	adjust_limit_rows_costs(&fc_pathnode->path.rows,
							&fc_pathnode->path.startup_cost,
							&fc_pathnode->path.total_cost,
							fc_offset_est, fc_count_est);

	return fc_pathnode;
}

/*
 * adjust_limit_rows_costs
 *	  根据OFFSET/LIMIT调整LimitPath节点的大小和成本估计。
 *
 * 如果我们处于顶层，它只是一个外观问题，但如果我们正在构建子查询，那么向外部规划器报告正确的信息是重要的。
 *
 * 当无法估计offset或count时，使用来自子路径的估计行数的10%。
 *
 * XXX 我们不考虑将offset/limit表达式本身的评估成本添加到路径成本中。理论上我们应该这样做，但在大多数情况下，这些表达式是微不足道的，不值得麻烦。
 */
void adjust_limit_rows_costs(double *fc_rows,	/* 输入/输出参数 */
						Cost *fc_startup_cost, /* 输入/输出参数 */
						Cost *fc_total_cost,	/* 输入/输出参数 */
						int64 fc_offset_est,
						int64 fc_count_est)
{
	double		fc_input_rows = *fc_rows;
	Cost		fc_input_startup_cost = *fc_startup_cost;
	Cost		fc_input_total_cost = *fc_total_cost;

	if (fc_offset_est != 0)
	{
		double		fc_offset_rows;

		if (fc_offset_est > 0)
			fc_offset_rows = (double) fc_offset_est;
		else
			fc_offset_rows = clamp_row_est(fc_input_rows * 0.10);
		if (fc_offset_rows > *fc_rows)
			fc_offset_rows = *fc_rows;
		if (fc_input_rows > 0)
			*fc_startup_cost +=
				(fc_input_total_cost - fc_input_startup_cost)
				* fc_offset_rows / fc_input_rows;
		*fc_rows -= fc_offset_rows;
		if (*fc_rows < 1)
			*fc_rows = 1;
	}

	if (fc_count_est != 0)
	{
		double		fc_count_rows;

		if (fc_count_est > 0)
			fc_count_rows = (double) fc_count_est;
		else
			fc_count_rows = clamp_row_est(fc_input_rows * 0.10);
		if (fc_count_rows > *fc_rows)
			fc_count_rows = *fc_rows;
		if (fc_input_rows > 0)
			*fc_total_cost = *fc_startup_cost +
				(fc_input_total_cost - fc_input_startup_cost)
				* fc_count_rows / fc_input_rows;
		*fc_rows = fc_count_rows;
		if (*fc_rows < 1)
			*fc_rows = 1;
	}
}


/*
 * reparameterize_path
 *		尝试修改路径以获得更大的参数化
 *
 * 我们使用这个来尝试将appendrel的所有子路径带到相同的参数化级别，确保它们都强制执行相同的连接资格（因此该参数化可以归因于由这些路径构建的追加路径）。 目前，仅支持少数几种路径类型，但可以根据需要添加更多。如果我们无法重新参数化给定路径，我们将返回NULL。
 *
 * 注意：我们故意不将创建的路径传递给add_path(); 这可能会试图删除它们，理由是它们的成本低于它们来自的路径，而我们不想这样。这里创建的路径不一定具有通用用途，但它们作为追加路径的成员可以是有用的。
 */
Path * reparameterize_path(PlannerInfo *fc_root, Path *fc_path,
					Relids fc_required_outer,
					double fc_loop_count)
{
	RelOptInfo *fc_rel = fc_path->parent;

	/* 只能增加，不能减少路径的参数化 */
	if (!bms_is_subset(PATH_REQ_OUTER(fc_path), fc_required_outer))
		return NULL;
	switch (fc_path->pathtype)
	{
		case T_SeqScan:
			return create_seqscan_path(fc_root, fc_rel, fc_required_outer, 0);
		case T_SampleScan:
			return (Path *) create_samplescan_path(fc_root, fc_rel, fc_required_outer);
		case T_IndexScan:
		case T_IndexOnlyScan:
			{
				IndexPath  *fc_ipath = (IndexPath *) fc_path;
				IndexPath  *fc_newpath = makeNode(IndexPath);

				/*
				 * 我们不能直接使用create_index_path，也不想这样做
				 * 因为它会重新计算indexqual条件，这是浪费精力。相反，我们稍微黑客一下：
				 * 扁平复制路径节点，修订其param_info，然后重新计算成本估计。
				 */
				memcpy(fc_newpath, fc_ipath, sizeof(IndexPath));
				fc_newpath->path.param_info =
					get_baserel_parampathinfo(fc_root, fc_rel, fc_required_outer);
				cost_index(fc_newpath, fc_root, fc_loop_count, false);
				return (Path *) fc_newpath;
			}
		case T_BitmapHeapScan:
			{
				BitmapHeapPath *fc_bpath = (BitmapHeapPath *) fc_path;

				return (Path *) create_bitmap_heap_path(fc_root,
														fc_rel,
														fc_bpath->bitmapqual,
														fc_required_outer,
														fc_loop_count, 0);
			}
		case T_SubqueryScan:
			{
				SubqueryScanPath *fc_spath = (SubqueryScanPath *) fc_path;

				return (Path *) create_subqueryscan_path(fc_root,
														 fc_rel,
														 fc_spath->subpath,
														 fc_spath->path.pathkeys,
														 fc_required_outer);
			}
		case T_Result:
			/* 仅支持RTE_RESULT扫描路径 */
			if (IsA(fc_path, Path))
				return create_resultscan_path(fc_root, fc_rel, fc_required_outer);
			break;
		case T_Append:
			{
				AppendPath *fc_apath = (AppendPath *) fc_path;
				List	   *fc_childpaths = NIL;
				List	   *fc_partialpaths = NIL;
				int			fc_i;
				ListCell   *fc_lc;

				/* 重新参数化子路径 */
				fc_i = 0;
				foreach(fc_lc, fc_apath->subpaths)
				{
					Path	   *fc_spath = (Path *) lfirst(fc_lc);

					fc_spath = reparameterize_path(fc_root, fc_spath,
												fc_required_outer,
												fc_loop_count);
					if (fc_spath == NULL)
						return NULL;
					/* 我们必须重新拆分常规路径和部分路径 */
					if (fc_i < fc_apath->first_partial_path)
						fc_childpaths = lappend(fc_childpaths, fc_spath);
					else
						fc_partialpaths = lappend(fc_partialpaths, fc_spath);
					fc_i++;
				}
				return (Path *)
					create_append_path(fc_root, fc_rel, fc_childpaths, fc_partialpaths,
									   fc_apath->path.pathkeys, fc_required_outer,
									   fc_apath->path.parallel_workers,
									   fc_apath->path.parallel_aware,
									   -1);
			}
		case T_Memoize:
			{
				MemoizePath *fc_mpath = (MemoizePath *) fc_path;
				Path	   *fc_spath = fc_mpath->subpath;

				fc_spath = reparameterize_path(fc_root, fc_spath,
											fc_required_outer,
											fc_loop_count);
				if (fc_spath == NULL)
					return NULL;
				return (Path *) create_memoize_path(fc_root, fc_rel,
													fc_spath,
													fc_mpath->param_exprs,
													fc_mpath->hash_operators,
													fc_mpath->singlerow,
													fc_mpath->binary_mode,
													fc_mpath->calls);
			}
		default:
			break;
	}
	return NULL;
}


/*
 * reparameterize_path_by_child
 * 		给定一个由给定子关系的父关系参数化的路径，将路径转换为由给定子关系参数化。
 *
 * 此函数会创建一个与给定路径相同类型的新路径，但由给定子关系参数化。大多数来自原始路径的字段可以简单地扁平复制，但任何表达式必须调整以引用正确的 varnos，并且任何路径必须递归重新参数化。其他引用特定 relids 的字段也需要进行调整。
 *
 * 成本、行数、宽度和并行路径属性依赖于 path->parent，该值在翻译过程中不会改变。因此，这些成员会原封不动地复制。
 *
 * 如果给定的路径无法重新参数化，该函数返回 NULL。
 */
Path * reparameterize_path_by_child(PlannerInfo *fc_root, Path *fc_path,
							 RelOptInfo *fc_child_rel)
{

#define FLAT_COPY_PATH(newnode, node, nodetype)  \
	( (newnode) = makeNode(nodetype), \
	  memcpy((newnode), (node), sizeof(nodetype)) )

#define ADJUST_CHILD_ATTRS(node) \
	((node) = \
	 (List *) adjust_appendrel_attrs_multilevel(fc_root, (Node *) (node), \
												fc_child_rel->relids, \
												fc_child_rel->top_parent_relids))

#define REPARAMETERIZE_CHILD_PATH(path) \
do { \
	(path) = reparameterize_path_by_child(fc_root, (path), fc_child_rel); \
	if ((path) == NULL) \
		return NULL; \
} while(0)

#define REPARAMETERIZE_CHILD_PATH_LIST(pathlist) \
do { \
	if ((pathlist) != NIL) \
	{ \
		(pathlist) = fc_reparameterize_pathlist_by_child(fc_root, (pathlist), \
													  fc_child_rel); \
		if ((pathlist) == NIL) \
			return NULL; \
	} \
} while(0)

	Path	   *fc_new_path;
	ParamPathInfo *fc_new_ppi;
	ParamPathInfo *fc_old_ppi;
	Relids		fc_required_outer;

	/*
	 * 如果该路径不是由给定关系的父级参数化，则不需要重新参数化。
	 */
	if (!fc_path->param_info ||
		!bms_overlap(PATH_REQ_OUTER(fc_path), fc_child_rel->top_parent_relids))
		return fc_path;

	/*
	 * 如果可能，重新参数化给定路径，制作一个副本。
	 *
	 * 此函数目前仅应用于通过分区连接代码进行分区的嵌套循环连接的内部侧。因此，我们只需要支持在该上下文中合理出现的路径类型。
	 * （特别是，支持排序路径类型将浪费代码和周期：即使我们在这里对它们进行转换，它们在后续成本比较中也会失去优势。）如果我们看到不支持的路径类型，
	 * 那就意味着我们将无法使用该路径类型生成分区连接计划。
	 */
	switch (nodeTag(fc_path))
	{
		case T_Path:

			/*
			 * 如果路径的限制子句包含对其他关系的侧向引用，我们不能重新参数化，因为我们不能在这里更改 RelOptInfo 的内容。
			 * （这样做会在我们使用非分区连接时破坏一些东西。）
			 */
			if (fc_ris_contain_references_to(fc_root,
										  fc_path->parent->baserestrictinfo,
										  fc_child_rel->top_parent_relids))
				return NULL;

			/*
			 * 如果这是一个带有引用其他关系的表样本参数的 SampleScan，我们无法重新参数化，因为我们不能在这里更改 RTE 的内容。
			 * （这样做会在我们使用非分区连接时破坏一些东西。）
			 */
			if (fc_path->pathtype == T_SampleScan)
			{
				Index		fc_scan_relid = fc_path->parent->relid;
				RangeTblEntry *fc_rte;

				/* 它应该是拥有 tablesample 子句的基础关系... */
				Assert(fc_scan_relid > 0);
				fc_rte = planner_rt_fetch(fc_scan_relid, fc_root);
				Assert(fc_rte->rtekind == RTE_RELATION);
				Assert(fc_rte->tablesample != NULL);

				if (fc_contain_references_to(fc_root, (Node *) fc_rte->tablesample,
										  fc_child_rel->top_parent_relids))
					return NULL;
			}

			FLAT_COPY_PATH(fc_new_path, fc_path, Path);
			break;

		case T_IndexPath:
			{
				IndexPath  *fc_ipath;

				/*
				 * 如果路径的限制子句包含对其他关系的侧向引用，我们不能重新参数化，因为我们不能在这里更改 IndexOptInfo 的内容。
				 * （这样做会在我们使用非分区连接时破坏一些东西。）
				 */
				if (fc_ris_contain_references_to(fc_root,
											  fc_path->parent->baserestrictinfo,
											  fc_child_rel->top_parent_relids))
					return NULL;

				FLAT_COPY_PATH(fc_ipath, fc_path, IndexPath);
				ADJUST_CHILD_ATTRS(fc_ipath->indexclauses);
				fc_new_path = (Path *) fc_ipath;
			}
			break;

		case T_BitmapHeapPath:
			{
				BitmapHeapPath *fc_bhpath;

				/*
				 * 如果路径的限制子句包含对其他关系的侧向引用，我们不能重新参数化，因为我们不能在这里更改 RelOptInfo 的内容。
				 * （这样做会在我们使用非分区连接时破坏一些东西。）
				 */
				if (fc_ris_contain_references_to(fc_root,
											  fc_path->parent->baserestrictinfo,
											  fc_child_rel->top_parent_relids))
					return NULL;

				FLAT_COPY_PATH(fc_bhpath, fc_path, BitmapHeapPath);
				REPARAMETERIZE_CHILD_PATH(fc_bhpath->bitmapqual);
				fc_new_path = (Path *) fc_bhpath;
			}
			break;

		case T_BitmapAndPath:
			{
				BitmapAndPath *fc_bapath;

				FLAT_COPY_PATH(fc_bapath, fc_path, BitmapAndPath);
				REPARAMETERIZE_CHILD_PATH_LIST(fc_bapath->bitmapquals);
				fc_new_path = (Path *) fc_bapath;
			}
			break;

		case T_BitmapOrPath:
			{
				BitmapOrPath *fc_bopath;

				FLAT_COPY_PATH(fc_bopath, fc_path, BitmapOrPath);
				REPARAMETERIZE_CHILD_PATH_LIST(fc_bopath->bitmapquals);
				fc_new_path = (Path *) fc_bopath;
			}
			break;

		case T_ForeignPath:
			{
				ForeignPath *fc_fpath;
				ReparameterizeForeignPathByChild_function fc_rfpc_func;

				/*
				 * 如果路径的限制子句包含对其他关系的侧向引用，我们不能重新参数化，因为我们不能在这里更改 RelOptInfo 的内容。
				 * （这样做会在我们使用非分区连接时破坏一些东西。）
				 */
				if (fc_ris_contain_references_to(fc_root,
											  fc_path->parent->baserestrictinfo,
											  fc_child_rel->top_parent_relids))
					return NULL;

				FLAT_COPY_PATH(fc_fpath, fc_path, ForeignPath);
				if (fc_fpath->fdw_outerpath)
					REPARAMETERIZE_CHILD_PATH(fc_fpath->fdw_outerpath);

				/* 如果需要，交给 FDW。 */
				fc_rfpc_func =
					fc_path->parent->fdwroutine->ReparameterizeForeignPathByChild;
				if (fc_rfpc_func)
					fc_fpath->fdw_private = fc_rfpc_func(fc_root, fc_fpath->fdw_private,
												   fc_child_rel);
				fc_new_path = (Path *) fc_fpath;
			}
			break;

		case T_CustomPath:
			{
				CustomPath *fc_cpath;

				/*
				 * 如果路径的限制子句包含对其他关系的侧向引用，我们不能重新参数化，因为我们不能在这里更改 RelOptInfo 的内容。
				 * （这样做会在我们使用非分区连接时破坏一些东西。）
				 */
				if (fc_ris_contain_references_to(fc_root,
											  fc_path->parent->baserestrictinfo,
											  fc_child_rel->top_parent_relids))
					return NULL;

				FLAT_COPY_PATH(fc_cpath, fc_path, CustomPath);
				REPARAMETERIZE_CHILD_PATH_LIST(fc_cpath->custom_paths);
				if (fc_cpath->methods &&
					fc_cpath->methods->ReparameterizeCustomPathByChild)
					fc_cpath->custom_private =
						fc_cpath->methods->ReparameterizeCustomPathByChild(fc_root,
																		fc_cpath->custom_private,
																		fc_child_rel);
				fc_new_path = (Path *) fc_cpath;
			}
			break;

		case T_NestPath:
			{
				JoinPath   *fc_jpath;
				NestPath   *fc_npath;

				FLAT_COPY_PATH(fc_npath, fc_path, NestPath);

				fc_jpath = (JoinPath *) fc_npath;
				REPARAMETERIZE_CHILD_PATH(fc_jpath->outerjoinpath);
				REPARAMETERIZE_CHILD_PATH(fc_jpath->innerjoinpath);
				ADJUST_CHILD_ATTRS(fc_jpath->joinrestrictinfo);
				fc_new_path = (Path *) fc_npath;
			}
			break;

		case T_MergePath:
			{
				JoinPath   *fc_jpath;
				MergePath  *fc_mpath;

				FLAT_COPY_PATH(fc_mpath, fc_path, MergePath);

				fc_jpath = (JoinPath *) fc_mpath;
				REPARAMETERIZE_CHILD_PATH(fc_jpath->outerjoinpath);
				REPARAMETERIZE_CHILD_PATH(fc_jpath->innerjoinpath);
				ADJUST_CHILD_ATTRS(fc_jpath->joinrestrictinfo);
				ADJUST_CHILD_ATTRS(fc_mpath->path_mergeclauses);
				fc_new_path = (Path *) fc_mpath;
			}
			break;

		case T_HashPath:
			{
				JoinPath   *fc_jpath;
				HashPath   *fc_hpath;

				FLAT_COPY_PATH(fc_hpath, fc_path, HashPath);

				fc_jpath = (JoinPath *) fc_hpath;
				REPARAMETERIZE_CHILD_PATH(fc_jpath->outerjoinpath);
				REPARAMETERIZE_CHILD_PATH(fc_jpath->innerjoinpath);
				ADJUST_CHILD_ATTRS(fc_jpath->joinrestrictinfo);
				ADJUST_CHILD_ATTRS(fc_hpath->path_hashclauses);
				fc_new_path = (Path *) fc_hpath;
			}
			break;

		case T_AppendPath:
			{
				AppendPath *fc_apath;

				FLAT_COPY_PATH(fc_apath, fc_path, AppendPath);
				REPARAMETERIZE_CHILD_PATH_LIST(fc_apath->subpaths);
				fc_new_path = (Path *) fc_apath;
			}
			break;

		case T_MemoizePath:
			{
				MemoizePath *fc_mpath;

				FLAT_COPY_PATH(fc_mpath, fc_path, MemoizePath);
				REPARAMETERIZE_CHILD_PATH(fc_mpath->subpath);
				ADJUST_CHILD_ATTRS(fc_mpath->param_exprs);
				fc_new_path = (Path *) fc_mpath;
			}
			break;

		case T_GatherPath:
			{
				GatherPath *fc_gpath;

				FLAT_COPY_PATH(fc_gpath, fc_path, GatherPath);
				REPARAMETERIZE_CHILD_PATH(fc_gpath->subpath);
				fc_new_path = (Path *) fc_gpath;
			}
			break;

		default:

			/* 我们不知道如何重新参数化这个路径。 */
			return NULL;
	}

	/*
	 * 调整参数化信息，该信息指向最上层的父级。最上层的父级可以与给定子级相距多个层级，因此使用多级表达式调整例程。
	 */
	fc_old_ppi = fc_new_path->param_info;
	fc_required_outer =
		adjust_child_relids_multilevel(fc_root, fc_old_ppi->ppi_req_outer,
									   fc_child_rel->relids,
									   fc_child_rel->top_parent_relids);

	/* 如果我们已经有了这个参数化的 PPI，就直接返回它 */
	fc_new_ppi = find_param_path_info(fc_new_path->parent, fc_required_outer);

	/*
	 * 如果没有，构建一个新的并将其链接到 PPI 列表。由于在 mark_dummy_rel() 中解释的相同原因，在给定 RelOptInfo 所在的上下文中分配新的 PPI。
	 */
	if (fc_new_ppi == NULL)
	{
		MemoryContext fc_oldcontext;
		RelOptInfo *fc_rel = fc_path->parent;

		fc_oldcontext = MemoryContextSwitchTo(GetMemoryChunkContext(fc_rel));

		fc_new_ppi = makeNode(ParamPathInfo);
		fc_new_ppi->ppi_req_outer = bms_copy(fc_required_outer);
		fc_new_ppi->ppi_rows = fc_old_ppi->ppi_rows;
		fc_new_ppi->ppi_clauses = fc_old_ppi->ppi_clauses;
		ADJUST_CHILD_ATTRS(fc_new_ppi->ppi_clauses);
		fc_rel->ppilist = lappend(fc_rel->ppilist, fc_new_ppi);

		MemoryContextSwitchTo(fc_oldcontext);
	}
	bms_free(fc_required_outer);

	fc_new_path->param_info = fc_new_ppi;

	/*
	 * 如果在目标列表中引用了外部关系的父级，则调整路径目标。当只有外部关系的父级在此关系中被侧向引用时，可能会发生这种情况。
	 */
	if (bms_overlap(fc_path->parent->lateral_relids,
					fc_child_rel->top_parent_relids))
	{
		fc_new_path->pathtarget = copy_pathtarget(fc_new_path->pathtarget);
		ADJUST_CHILD_ATTRS(fc_new_path->pathtarget->exprs);
	}

	return fc_new_path;
}

/*
 * reparameterize_pathlist_by_child
 * 		帮助函数，用于通过给定子关系重新参数化路径列表。
 */
static List * fc_reparameterize_pathlist_by_child(PlannerInfo *fc_root,
								 List *fc_pathlist,
								 RelOptInfo *fc_child_rel)
{
	ListCell   *fc_lc;
	List	   *fc_result = NIL;

	foreach(fc_lc, fc_pathlist)
	{
		Path	   *fc_path = reparameterize_path_by_child(fc_root, lfirst(fc_lc),
														fc_child_rel);

		if (fc_path == NULL)
		{
			list_free(fc_result);
			return NIL;
		}

		fc_result = lappend(fc_result, fc_path);
	}

	return fc_result;
}

/*
 * contain_references_to
 *		检测给定子句中的任何 Vars 或 PlaceHolderVars 是否包含对给定 'relids' 的侧向引用。
 */
static bool fc_contain_references_to(PlannerInfo *fc_root, Node *fc_clause, Relids fc_relids)
{
	bool		fc_ret = false;
	List	   *fc_vars;
	ListCell   *fc_lc;

	/*
	 * 检查子句中使用的所有 Vars 和 PlaceHolderVars。
	 *
	 * 通过省略相关标志，这也给我们提供了一个廉价的理智检查，以确认子句中没有出现聚合或窗口函数。我们不期望在扫描级别的限制或表样本中看到这些。
	 */
	fc_vars = pull_var_clause(fc_clause, PVC_INCLUDE_PLACEHOLDERS);
	foreach(fc_lc, fc_vars)
	{
		Node	   *fc_node = (Node *) lfirst(fc_lc);

		if (IsA(fc_node, Var))
		{
			Var		   *fc_var = (Var *) fc_node;

			if (bms_is_member(fc_var->varno, fc_relids))
			{
				fc_ret = true;
				break;
			}
		}
		else if (IsA(fc_node, PlaceHolderVar))
		{
			PlaceHolderVar *fc_phv = (PlaceHolderVar *) fc_node;
			PlaceHolderInfo *fc_phinfo = find_placeholder_info(fc_root, fc_phv, false);

			/*
			 * 我们应该检查 ph_eval_at（以防 PHV 是在其他关系中计算的，然后在此处进行侧向引用）和 ph_lateral（以防 PHV 在此处进行评估，但包含对其他关系的侧向引用）。
			 * 前一种情况不应在 baserestrictinfo 子句中出现，但可以在表样本子句中出现。
			 */
			if (bms_overlap(fc_phinfo->ph_eval_at, fc_relids) ||
				bms_overlap(fc_phinfo->ph_lateral, fc_relids))
			{
				fc_ret = true;
				break;
			}
		}
		else
			Assert(false);
	}

	list_free(fc_vars);

	return fc_ret;
}

/*
 * ris_contain_references_to
 *		对一个 RestrictInfos 列表应用 contain_references_to()。
 *
 * 我们需要额外的代码，因为 pull_var_clause() 无法深入
 * RestrictInfos。
 */
static bool fc_ris_contain_references_to(PlannerInfo *fc_root, List *fc_rinfos, Relids fc_relids)
{
	ListCell   *fc_lc;

	foreach(fc_lc, fc_rinfos)
	{
		RestrictInfo *fc_rinfo = lfirst_node(RestrictInfo, fc_lc);

		/* 伪常量子句不能包含任何变量或 PHVs */
		if (fc_rinfo->pseudoconstant)
			continue;
		if (fc_contain_references_to(fc_root, (Node *) fc_rinfo->clause, fc_relids))
			return true;
	}
	return false;
}
